julia笔记
由于julia和python/ruby/perl非常像,没什么可以整理的,所以这里只做记录备忘
环境搭建
安装julia 进入repl 按]进入安装包模式
add IJulia
由于julia和python/ruby/perl非常像,没什么可以整理的,所以这里只做记录备忘
环境搭建
安装julia 进入repl 按]进入安装包模式
add IJulia
我本身有啥语言都会点,所以这门语言我会用其他语言的特性来描述,请谨慎阅读
基本抄自https://gfw.go101.org/ 值得一看
golang槽点太多。写出坑来都毫无感觉
[toc]
研究一个语言要关注哪些地方?
下载包
go get github.com/onsi/gomega
如果离线安装,得克隆到goroot目录里面
更新mod
go mod tidy
select-case
分支流程控制代码块Go中有一个专门为通道设计的
select-case
分支流程控制语法。 此语法和switch-case
分支流程控制语法很相似。 比如,select-case
流程控制代码块中也可以有若干case
分支和最多一个default
分支。 但是,这两种流程控制也有很多不同点。在一个select-case
流程控制中,
select
关键字和{
之间不允许存在任何表达式和语句。fallthrough
语句不能被使用.- 每个
case
关键字后必须跟随一个通道接收数据操作或者一个通道发送数据操作。 通道接收数据操作可以做为源值出现在一条简单赋值语句中。 以后,一个case
关键字后跟随的通道操作将被称为一个case
操作。- 所有的非阻塞
case
操作中将有一个被随机选择执行(而不是按照从上到下的顺序),然后执行此操作对应的case
分支代码块。- 在所有的
case
操作均为阻塞的情况下,如果default
分支存在,则default
分支代码块将得到执行; 否则,当前协程将被推入所有阻塞操作中相关的通道的发送数据协程队列或者接收数据协程队列中,并进入阻塞状态。按照上述规则,一个不含任何分支的
select-case
代码块select{}
将使当前协程处于永久阻塞状态。
select-case
流程控制的实现机理
select-case
流程控制是Go中的一个重要和独特的特性。 下面列出了官方标准运行时中select-case
流程控制的实现步骤。
- 将所有
case
操作中涉及到的通道表达式和发送值表达式按照从上到下,从左到右的顺序一一估值。 在赋值语句中做为源值的数据接收操作对应的目标值在此时刻不需要被估值。- 将所有分支随机排序。
default
分支总是排在最后。 所有case
操作中相关的通道可能会有重复的。- 为了防止在下一步中造成(和其它协程互相)死锁,对所有
case
操作中相关的通道进行排序。 排序依据并不重要,官方Go标准编译器使用通道的地址顺序进行排序。 排序结果中前N
个通道不存在重复的情况。N
为所有case
操作中涉及到的不重复的通道的数量。 下面,通道锁顺序***是针对此排序结果中的前N
个通道来说的,通道锁逆序***是指此顺序的逆序。- 按照上一步中的生成通道锁顺序获取所有相关的通道的锁。
按照第2步中生成的分支顺序检查相应分支:
- 如果这是一个
case
分支并且相应的通道操作是一个向关闭了的通道发送数据操作,则按照通道锁逆序解锁所有的通道并在当前协程中产生一个恐慌。 跳到第12步。- 如果这是一个
case
分支并且相应的通道操作是非阻塞的,则按照通道锁逆序解锁所有的通道并执行相应的case
分支代码块。 (此相应的通道操作可能会唤醒另一个处于阻塞状态的协程。) 跳到第12步。如果这是
default
分支,则按照通道锁逆序解锁所有的通道并执行此default
分支代码块。 跳到第12步。(到这里,default分支肯定是不存在的,并且所有的case操作均为阻塞的。)
- 将当前协程(和对应
case
分支信息)推入到每个case
操作中对应的通道的发送数据协程队列或接收数据协程队列中。 当前协程可能会被多次推入到同一个通道的这两个队列中,因为多个case
操作中对应的通道可能为同一个。- 使当前协程进入阻塞状态并且按照通道锁逆序解锁所有的通道。
- …,当前协程处于阻塞状态,等待其它协程通过通道操作唤醒当前协程,…
- 当前协程被另一个协程中的一个通道操作唤醒。 此唤醒通道操作可能是一个通道关闭操作,也可能是一个数据发送/接收操作。 如果它是一个数据发送/接收操作,则(当前正被解释的
select-case
流程中)肯定有一个相应case
操作与之配合传递数据。 在此配合过程中,当前协程将从相应case
操作相关的通道的接收/发送数据协程队列中弹出。- 按照第3步中的生成的通道锁顺序获取所有相关的通道的锁。
将当前协程从各个case
操作中对应的通道的发送数据协程队列或接收数据协程队列中(可能以非弹出的方式)移除。
- 如果当前协程是被一个通道关闭操作所唤醒,则跳到第5步。
- 如果当前协程是被一个数据发送/接收操作所唤醒,则相应的
case
分支已经在第9步中知晓。 按照通道锁逆序解锁所有的通道并执行此case
分支代码块。- 完毕。
从此实现中,我们得知
- 一个协程可能同时多次处于同一个通道的发送数据协程队列或接收数据协程队列中。
- 当一个协程被阻塞在一个
select-case
流程控制中并在以后被唤醒时,它可能会从多个通道的发送数据协程队列和接收数据协程队列中被移除。
channel简单用法
通知
package main
import (
"crypto/rand"
"fmt"
"os"
"sort"
)
func main() {
values := make([]byte, 32 * 1024 * 1024)
if _, err := rand.Read(values); err != nil {
fmt.Println(err)
os.Exit(1)
}
done := make(chan struct{}) // 也可以是缓冲的
// 排序协程
go func() {
sort.Slice(values, func(i, j int) bool {
return values[i] < values[j]
})
done <- struct{}{} // 通知排序已完成
}()
// 并发地做一些其它事情...
<- done // 等待通知
fmt.Println(values[0], values[len(values)-1])
}
package main
import "log"
import "time"
type T = struct{}
func worker(id int, ready <-chan T, done chan<- T) {
<-ready // 阻塞在此,等待通知
log.Print("Worker#", id, "开始工作")
// 模拟一个工作负载。
time.Sleep(time.Second * time.Duration(id+1))
log.Print("Worker#", id, "工作完成")
done <- T{} // 通知主协程(N-to-1)
}
func main() {
log.SetFlags(0)
ready, done := make(chan T), make(chan T)
go worker(0, ready, done)
go worker(1, ready, done)
go worker(2, ready, done)
// 模拟一个初始化过程
time.Sleep(time.Second * 3 / 2)
// 单对多通知
close(ready)
// 等待被多对单通知
<-done; <-done; <-done
}
assert.Equal(t, (*metadata)(nil), mymetadata, "meta nill check")
这里有个解决方案https://zhuanlan.zhihu.com/p/351428978
for i := 0; i < 10; i++ {
go func(x int) {
fmt.Println(x)
}(i)
}
我遇到的场景,是time保存到mongo在读回来,就多了location信息,不能直接比较,不相等
Error: Not equal:
expected: time.Time{wall:0xa4fc540, ext:63761777875, loc:(*time.Location)(nil)}
actual : time.Time{wall:0xc0338154ca582af8, ext:10214780624, loc:(*time.Location)(0x15b1ee0)}
Diff:
--- Expected
+++ Actual
@@ -1,5 +1,136 @@
(time.Time) {
- wall: (uint64) 173000000,
- ext: (int64) 63761777875,
- loc: (*time.Location)(<nil>)
+ wall: (uint64) 13849555480266418936,
+ ext: (int64) 10214780624,
+ loc: (*time.Location)({
+ name: (string) (len=5) "Local",
+ zone: ([]time.zone) (len=3) {
+ (time.zone) {
+ name: (string) (len=3) "LMT",
+ offset: (int) 29143,
+ isDST: (bool) false
+ },
//以下省略一百行timezone信息,太傻逼了
查文档,用time.equal,也不好使,最终用unix来比较
assert.Equal(t, doc.CreateTime.Unix(), createtime.Unix(), "time check error")
忙活一晚上
package main
import (
"fmt"
)
type Person struct {
}
func main() {
var st Person
if (Person{} == st) {
fmt.Println("It is an empty structure")
} else {
fmt.Println("It is not an empty structure")
}
}
如果结构体比较复杂不能直接比较,用deepEqual
package main
import (
"fmt"
"reflect"
)
type Person struct {
age int
}
func (x Person) IsStructureEmpty() bool {
return reflect.DeepEqual(x, Person{})
}
func main() {
x := Person{}
if x.IsStructureEmpty() {
fmt.Println("Structure is empty")
} else {
fmt.Println("Structure is not empty")
}
}
goroutine 718 [running]: runtime.systemstack_switch() /usr/local/go/src/runtime/asm_amd64.s:330 fp=0xc000a39e08 sp=0xc000a39e00 pc=0x47afe0 runtime.mallocgc(0x5ff45b34d, 0xe1ac60, 0x1, 0x2b) /usr/local/go/src/runtime/malloc.go:1070 +0x7e6 fp=0xc000a39ea8 sp=0xc000a39e08 pc=0x410f06 runtime.makeslice(0xe1ac60, 0x5ff45b34d, 0x5ff45b34d, 0x2) /usr/local/go/src/runtime/slice.go:98 +0x6f fp=0xc000a39ee0 sp=0xc000a39ea8 pc=0x45a18f bytes.makeSlice(0x5ff45b34d, 0x0, 0x0, 0x0) /usr/local/go/src/bytes/buffer.go:229 +0x73 fp=0xc000a39f20 sp=0xc000a39ee0 pc=0x5143f3 bytes.(*Buffer).grow(0xc000726630, 0x2b, 0x0) /usr/local/go/src/bytes/buffer.go:142 +0x15c fp=0xc000a39f70 sp=0xc000a39f20 pc=0x513d3c bytes.(*Buffer).Write(0xc000726630, 0xc6fbba4d20, 0x2b, 0x2b, 0xc6fbba4d20, 0x2b, 0x2b) /usr/local/go/src/bytes/buffer.go:172 +0xe5 fp=0xc000a39fa8 sp=0xc000a39f70 pc=0x514065 github.com/davecgh/go-spew/spew.(*dumpState).indent(0xc000a40ab0) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:67 +0xc2 fp=0xc000a3a010 sp=0xc000a39fa8 pc=0xd5aca2 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef4d60, 0xc0001d8640, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:416 +0x3a6 fp=0xc000a3a188 sp=0xc000a3a010 pc=0xd5c2e6 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe93400, 0xc0001d8640, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3a300 sp=0xc000a3a188 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xeb2060, 0xc0001d8640, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3a478 sp=0xc000a3a300 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpSlice(0xc000a40ab0, 0xe087c0, 0xc000198bb8, 0x1b7) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:238 +0x125 fp=0xc000a3a590 sp=0xc000a3a478 pc=0xd5b845 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe087c0, 0xc000198bb8, 0x1b7) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:352 +0x9e6 fp=0xc000a3a708 sp=0xc000a3a590 pc=0xd5c926 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef4ee0, 0xc000198bb8, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3a880 sp=0xc000a3a708 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef4e20, 0xc000198bb0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3a9f8 sp=0xc000a3a880 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xdf43c0, 0xc000034538, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3ab28 sp=0xc000a3a9f8 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xdf43c0, 0xc000034538, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3aca0 sp=0xc000a3ab28 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xed34e0, 0xc000034500, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3ae18 sp=0xc000a3aca0 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpSlice(0xc000a40ab0, 0xe08780, 0xc00015d718, 0x1b7) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:238 +0x125 fp=0xc000a3af30 sp=0xc000a3ae18 pc=0xd5b845 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe08780, 0xc00015d718, 0x1b7) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:352 +0x9e6 fp=0xc000a3b0a8 sp=0xc000a3af30 pc=0xd5c926 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf09da0, 0xc00015d6c0, 0x1f9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3b220 sp=0xc000a3b0a8 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf09a20, 0xc00015d6c0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3b398 sp=0xc000a3b220 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xf6b5a0, 0xc0001db790, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3b4c8 sp=0xc000a3b398 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf6b5a0, 0xc0001db790, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3b640 sp=0xc000a3b4c8 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef4d60, 0xc0001db780, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3b7b8 sp=0xc000a3b640 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe93400, 0xc0001db780, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3b930 sp=0xc000a3b7b8 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xeb2060, 0xc0001db780, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3baa8 sp=0xc000a3b930 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpSlice(0xc000a40ab0, 0xe087c0, 0xc000198c68, 0x1b7) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:238 +0x125 fp=0xc000a3bbc0 sp=0xc000a3baa8 pc=0xd5b845 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe087c0, 0xc000198c68, 0x1b7) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:352 +0x9e6 fp=0xc000a3bd38 sp=0xc000a3bbc0 pc=0xd5c926 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef4ee0, 0xc000198c68, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3beb0 sp=0xc000a3bd38 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef4e20, 0xc000198c60, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3c028 sp=0xc000a3beb0 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xdf43c0, 0xc000034578, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3c158 sp=0xc000a3c028 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xdf43c0, 0xc000034578, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3c2d0 sp=0xc000a3c158 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xed34e0, 0xc000034540, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3c448 sp=0xc000a3c2d0 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xf3f340, 0xc000034540, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3c578 sp=0xc000a3c448 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf3f340, 0xc000034540, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3c6f0 sp=0xc000a3c578 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef4d60, 0xc0001db740, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3c868 sp=0xc000a3c6f0 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe93400, 0xc0001db740, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3c9e0 sp=0xc000a3c868 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xeb2060, 0xc0001db740, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3cb58 sp=0xc000a3c9e0 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xf23c00, 0xc0001db740, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3cc88 sp=0xc000a3cb58 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf23c00, 0xc0001db740, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3ce00 sp=0xc000a3cc88 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe631c0, 0xc00007c420, 0x1b5) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:394 +0xe25 fp=0xc000a3cf78 sp=0xc000a3ce00 pc=0xd5cd65 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xeb33c0, 0xc00007c420, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3d0f0 sp=0xc000a3cf78 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xefc400, 0xc00007c420, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3d220 sp=0xc000a3d0f0 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xefc400, 0xc00007c420, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3d398 sp=0xc000a3d220 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf09e80, 0xc00034d2c0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3d510 sp=0xc000a3d398 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xebb380, 0xc00034d2c0, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3d640 sp=0xc000a3d510 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xebb380, 0xc00034d2c0, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3d7b8 sp=0xc000a3d640 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf33260, 0xc0002488c0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3d930 sp=0xc000a3d7b8 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf09da0, 0xc0002488c0, 0x1f9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3daa8 sp=0xc000a3d930 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf09a20, 0xc0002488c0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3dc20 sp=0xc000a3daa8 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xf6b5a0, 0xc000196360, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3dd50 sp=0xc000a3dc20 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf6b5a0, 0xc000196360, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3dec8 sp=0xc000a3dd50 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef4d60, 0xc000196350, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3e040 sp=0xc000a3dec8 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe93400, 0xc000196350, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3e1b8 sp=0xc000a3e040 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xed3ae0, 0xc000196350, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3e330 sp=0xc000a3e1b8 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xf5fd00, 0xc000196350, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3e460 sp=0xc000a3e330 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf5fd00, 0xc000196350, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3e5d8 sp=0xc000a3e460 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf28880, 0xc0001b6488, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3e750 sp=0xc000a3e5d8 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xf6b840, 0xc0001b6488, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3e880 sp=0xc000a3e750 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf6b840, 0xc0001b6488, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3e9f8 sp=0xc000a3e880 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe7f000, 0xc0001161b0, 0x1b5) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:394 +0xe25 fp=0xc000a3eb70 sp=0xc000a3e9f8 pc=0xd5cd65 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf0a200, 0xc0001161b0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3ece8 sp=0xc000a3eb70 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xf3f4a0, 0xc0001161b0, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3ee18 sp=0xc000a3ece8 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc0004f6ab0, 0xf3f4a0, 0xc0001161b0, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3ef90 sp=0xc000a3ee18 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf33260, 0xc00015cfc0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3f108 sp=0xc000a3ef90 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf09da0, 0xc00015cfc0, 0x1f9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3f280 sp=0xc000a3f108 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf09a20, 0xc00015cfc0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3f3f8 sp=0xc000a3f280 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xf6b5a0, 0xc00017b2e0, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3f528 sp=0xc000a3f3f8 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc0004f6ab0, 0xf6b5a0, 0xc00017b2e0, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3f6a0 sp=0xc000a3f528 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef4d60, 0xc00017b2d0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3f818 sp=0xc000a3f6a0 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe93400, 0xc00017b2d0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3f990 sp=0xc000a3f818 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xed3ae0, 0xc00017b2d0, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3fb08 sp=0xc000a3f990 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xf5fd00, 0xc00017b2d0, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a3fc38 sp=0xc000a3fb08 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc0004f6ab0, 0xf5fd00, 0xc00017b2d0, 0x36) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a3fdb0 sp=0xc000a3fc38 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf28880, 0xc000123148, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a3ff28 sp=0xc000a3fdb0 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xf6b840, 0xc000828d80, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a40058 sp=0xc000a3ff28 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc0004f6ab0, 0xf6b840, 0xc000828d80, 0x1b6) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a401d0 sp=0xc000a40058 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef52a0, 0xc000828d80, 0x1b9) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a40348 sp=0xc000a401d0 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xef0620, 0xc000828d80, 0x199) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a404c0 sp=0xc000a40348 pc=0xd5c474 github.com/davecgh/go-spew/spew.(*dumpState).dumpPtr(0xc000a40ab0, 0xee9ea0, 0xc000828d80, 0x16) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:154 +0x7f8 fp=0xc000a405f0 sp=0xc000a404c0 pc=0xd5b5b8 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc0004f6ab0, 0xee9ea0, 0xc000828d80, 0x16) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:262 +0x1765 fp=0xc000a40768 sp=0xc000a405f0 pc=0xd5d6a5 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xe64f00, 0xc0006580c0, 0x95) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:394 +0xe25 fp=0xc000a408e0 sp=0xc000a40768 pc=0xd5cd65 github.com/davecgh/go-spew/spew.(*dumpState).dump(0xc000a40ab0, 0xf73140, 0xc000658000, 0x99) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:421 +0x534 fp=0xc000a40a58 sp=0xc000a408e0 pc=0xd5c474 github.com/davecgh/go-spew/spew.fdump(0x161e140, 0x1111720, 0xc000726630, 0xc000a40c30, 0x1, 0x1) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/dump.go:465 +0x125 fp=0xc000a40af0 sp=0xc000a40a58 pc=0xd5d845 github.com/davecgh/go-spew/spew.(*ConfigState).Sdump(0x161e140, 0xc0004f6c30, 0x1, 0x1, 0x19, 0x0) /root/go/pkg/mod/github.com/davecgh/go-spew@v1.1.1/spew/config.go:281 +0x78 fp=0xc000a40b38 sp=0xc000a40af0 pc=0xd5aa58 github.com/stretchr/testify/assert.diff(0xf73140, 0xc000658000, 0xf73140, 0xc0006581c0, 0x0, 0x0) /root/go/pkg/mod/github.com/stretchr/testify@v1.6.1/assert/assertions.go:1592 +0x1d8 fp=0xc000a40cd8 sp=0xc000a40b38 pc=0xd66e38 github.com/stretchr/testify/assert.Equal(0x1114680, 0xc00054bb00, 0xf73140, 0xc000658000, 0xf73140, 0xc0006581c0, 0xc000a41078, 0x1, 0x1, 0x1)
cannot refer to unexported name xxxx
错误变量覆盖,深坑,没告警
func (mgr *Manager) fixRunningTask() {
db := store.GetMongo()
docs, err := db.GetNoHeartbeatTask()
if err != nil {
return
}
//proxy连接代码,省略
for _, doc := range docs {
task := task.NewTask(doc)
ErrCount := 0
var res error
for {
err := task.SyncResult(proxy, true, opts...)
if err == nil || err != ErrSystem {
// res = task.SyncResult(proxy, true, opts...)
//if res == nil || res != common.ErrSystem {
break
}
// 如果是框架超时等错误,多试几次
ErrCount++
if ErrCount > 10 {
break
}
}
if err != nil && err != ErrStillRunning {
// if res != nil && res != ErrStillRunning {
// 可能发生了迁移或者重启,重新做
db.InitTask(...)
}
}
}
上面的err,覆盖了下面的err。导致永远不会执行InitTask
c/c++程序员习惯于编译器纠错,但go编译器一点反应都没
一个字符数组默认引用的例子
start := Partition.Range.Start
startUint32 := binary.LittleEndian.Uint32(start)
binary.BigEndian.PutUint32(start, startUint32)
这么改,start和 Partition.Range.Start实际上是一个东西
这段代码这么看没啥问题,如果是个复杂一点的逻辑,就会被坑到,比如后面又改了start,但结果把上面的一起改了
go pprof抓内存泄漏
curl localhost:6060/debug/pprof/heap >base.out
# 喝口水,上个厕所
curl localhost:6060/debug/pprof/heap >current.out
go tool pprof -base base.out current.out
# 进入交互
(pprof) top10
(pprof) tree
基本就能查到,还算好用
看懂堆栈
// Declaration
main.Example(slice []string, str string, i int)
// Stack trace
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
类型 + 长度
我本身有啥语言都会点,所以这门语言我会用其他语言的特性来描述,请谨慎阅读
安装
curl https://dlang.org/install.sh | bash -s
基本的工具 dmd编译,rdmd可以当成shell脚本使用#!/usr/bin/env rdmd
包管理工具dub
包引用语法 import std.stdio : writeln, writefln;
基本上大同小异
类型,完全等同于c/c++但是存在构造函数.init
.max
.nan
等等,构造函数也是一种属性,.stringof
返回自身名字。感觉这很反射,python也有__repr__
@system
默认 @safe
检查内存安全@trusted
三方api互通有点像rust的unsafe
auto add(T)(T lhs, T rhs) {
return lhs + rhs;
}
struct S(T) {
// ...
}
大概就这么多
我本身有啥语言都会点,所以这门语言我会用其他语言的特性来描述,请谨慎阅读
注释 # /discord”””
字符串 和c++差不多,有raw字符串 r”blahbah\balh”
var 变量定义,有点像rust的let 可以指定类型,以及用值初始化
const常量 表示的编译期常量
数字 科学表示法
1_000_000
1.0e9
0x
,二进制字面值用 0b
,八进制用 0o
单独一个前导零不产生八进制,和c++不一样let
x = 0 # x是 ``int``
y = 0'i8 # y是 ``int8``
z = 0'i64 # z是 ``int64``
u = 0'u # u是 ``uint`
var
a = 0.0 # a是 ``float``
b = 0.0'f32 # b是 ``float32``
c = 0.0'f64 # c是 ``float64``
控制流 所有的控制条件都不需要括号,有点像python
let name = readLine(stdin)
if name == "":
echo "Poor soul, you lost your name?"
elif name == "name":
echo "Very funny, your name is name."
else:
echo "Hi, ", name, "!"
let name = readLine(stdin)
case name
of "":
echo "Poor soul, you lost your name?"
of "name":
echo "Very funny, your name is name."
of "Dave", "Frank":
echo "Cool name!"
else:
echo "Hi, ", name, "!"
echo "Counting to ten: "
for i in countup(1, 10):
echo i
#语法糖
for i in 0..<10:
... # 0..9
var s = "some string"
for i in 0..<s.len:
...
for index, item in ["a","b"].pairs:
echo item, " at index ", index
# => a at index 0
# => b at index 1
when system.hostOS == "windows":
echo "running on Windows!"
elif system.hostOS == "linux":
echo "running on Linux!"
elif system.hostOS == "macosx":
echo "running on Mac OS X!"
else:
echo "unknown operating system"
proc yes(question: string): bool =
echo question, " (y/n)"
while true:
case readLine(stdin)
of "y", "Y", "yes", "Yes": return true
of "n", "N", "no", "No": return false
else: echo "Please be clear: yes or no"
if yes("Should I delete all your important files?"):
echo "I'm sorry Dave, I'm afraid I can't do that."
else:
echo "I think you know what the problem is just as well as I do."
这个函数声明,像不像go/rust
fn add(a: i32, b: i32) -> i32 {
return a + b;
}
//func 函数名(形式参数列表)(返回值列表){
// 函数体
//}
func hypot(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
这样设计,我就当parser好写了
注意 返回值不用必须显式舍弃 或者用修饰
discard yes("May I ask a pointless question?")
proc p(x, y: int): int {.discardable.} =
return x + y
p(3, 4) # now valid
proc `$` (x: myDataType): string = ...
# 现在$操作符对myDataType生效,重载解析确保$对内置类型像之前一样工作。
#"``"标记也可以来用调用一个像任何其它过程的操作符:
if `==`( `+`(3, 4), 7): echo "True"
函数也需要前向声明 (c/c++陋习)
iterator countup(a, b: int): int =
var res = a
while res <= b:
yield res
inc(res)
还支持引用和切片,有点像c++/go 跳过了
还提供模版和泛型,更像c++了
多态,method
宏比较暴力,不仔细讲
如何对nim做贡献/输出? 引自 https://dev.to/xflywind/how-to-contribute-to-nim-language-4ad8
先看 Nim 文档 .
然后去处理 easy问题 或者丰富文档 documentation.
加功能,写库,可能要看RFC).
解决更复杂的问题,去处理JS
codegen
标记的问题
Contributing Guide 文档一定要看
我本身有啥语言都会点,所以这门语言我会用其他语言的特性来描述,请谨慎阅读
注释 # =begin =end
所有都是对象,要了解内嵌方法
"Hello".method(:class).class #=> Method
1.+(3) #=> 4
10.* 5 #=> 50
100.methods.include?(:/) #=> true
控制流 所有控制块都用end结尾 条件不用括号
counter = 1
while counter <= 5 do
puts "iteration #{counter}"
counter += 1
end
for counter in 1..5
puts "iteration #{counter}"
end
(1..5).each do |counter|
puts "iteration #{counter}"
end
array.each do |element|
puts "#{element} is part of the array"
end
hash.each do |key, value|
puts "#{key} is #{value}"
end
这个each do后面的是lambda。就是语法有点怪异,c++的lambda
std::vector<int> v;
std::for_each(v.begin(),v.end(), [](int i){ std::cout<<i;});
for(auto var : v) {
std::cout<<var;
}
c++的lambda的参数比较明显,是个函数调用,ruby这个竖线包围有点不明不白, 更像range-for这种用法
ruby甚至还有each_with_index
# 如果你还需要索引值,可以使用 "each_with_index",并且定义
# 一个索引变量
array.each_with_index do |element, index|
puts "#{element} is number #{index} in the array"
end
map reduce也是内嵌的。这点python也有,更泛型一些,而不是作为对象方法,ruby就是这么设计的
array = [1,2,3,4,5]
doubled = array.map do |element|
element * 2
end
puts doubled
#=> [2,4,6,8,10]
puts array
#=> [1,2,3,4,5]
case when对应switch-case
异常处理,python的try-except-finally对应begin-rescue-ensure
begin
raise NoMemoryError, 'You ran out of memory.'
rescue NoMemoryError => exception_variable
puts 'NoMemoryError was raised', exception_variable
rescue RuntimeError => other_exception_variable
puts 'RuntimeError was raised now'
else
puts 'This runs if no exceptions were thrown at all'
ensure
puts 'This code always runs no matter what'
end
函数 可以不用括号
def surround
puts "{"
yield
puts "}"
end
surround { puts 'hello world' }
# {
# hello world
# }
# => nil
# 可以向函数传递一个块
# "&"标记传递的块是一个引用
def guests(&block)
block.class #=> Proc
block.call(4)
end
guests { |n| "You have #{n} guests." }
# => "You have 4 guests."
# 可以传递多个参数,这些参数会转成一个数组,
# 这也是使用星号符 ("*") 的原因:
def guests(*array)
array.each { |guest| puts guest }
end
class Human
# 一个类变量,它被这个类的所有实例变量共享
@@species = "H. sapiens"
# 基本构造函数
def initialize(name, age = 0)
# 将参数值赋给实例变量 "name"
@name = name
# 如果没有给出 age,那么会采用参数列表中的默认值
@age = age
end
# 基本的 setter 方法
def name=(name)
@name = name
end
# 基本地 getter 方法
def name
@name
end
# 以上的功能也可以用下面的 attr_accessor 来封装
attr_accessor :name
# Getter/setter 方法也可以像这样单独创建
attr_reader :name
attr_writer :name
# 类方法通过使用 self 与实例方法区别开来。
# 它只能通过类来调用,不能通过实例调用。
def self.say(msg)
puts "#{msg}"
end
def species
@@species
end
end
对比c++来解释,@@是静态变量,实例共享主要是setter getter设计比较语法糖,齁得慌
继承 <
class Doctor < Human
end
包含和扩展,这个也是语法糖
# '包含'模块后,模块的方法会绑定为类的实例方法
# '扩展'模块后,模块的方法会绑定为类方法
class Person
include ModuleExample
end
class Book
extend ModuleExample
end
Person.foo # => NoMethodError: undefined method `foo' for Person:Class
Person.new.foo # => 'foo'
Book.foo # => 'foo'
Book.new.foo # => NoMethodError: undefined method `foo'
一般都继承Stucture,类似python继承object
转自这个链接 http://mysql.taobao.org/monthly/2019/01/01/
在数据库系统发展的历史长河中,故障恢复问题始终伴随左右,也深刻影响着数据库结构的发展变化。通过故障恢复机制,可以实现数据库的两个至关重要的特性:Durability of Updates以及Failure Atomic,也就是我们常说的的ACID中的A和D。磁盘数据库由于其卓越的性价比一直以来都占据数据库应用的主流位置。然而,由于需要协调内存和磁盘两种截然不同的存储介质,在处理故障恢复问题时也增加了很多的复杂度。随着学术界及工程界的共同努力及硬件本身的变化,磁盘数据库的故障恢复机制也不断的迭代更新,尤其近些年来,随着NVM的浮现,围绕新硬件的研究也如雨后春笋出现。本文希望通过分析不同时间点的关键研究成果,来梳理数据库故障恢复问题的本质,其发展及优化方向,以及随着硬件变化而发生的变化。 文章将首先描述故障恢复问题本身;然后按照基本的时间顺序介绍传统数据库中故障恢复机制的演进及优化;之后思考新硬件带来的机遇与挑战;并引出围绕新硬件的两个不同方向的研究成果;最后进行总结。
数据库系统运行过程中可能遇到的故障类型主要包括,Transaction Failure,Process Failure,System Failure以及Media Failure。其中Transaction Failure可能是主动回滚或者冲突后强制Abort;Process Failure指的是由于各种原因导致的进程退出,进程内存内容会丢失;System Failure来源于操作系统或硬件故障;而Media Failure则是存储介质的不可恢复损坏。数据库系统需要正确合理的处理这些故障,从而保证系统的正确性。为此需要提供两个特性:
因此,故障恢复的问题描述为:即使在出现故障的情况下,数据库依然能够通过提供Durability及Atomic特性,保证恢复后的数据库状态正确。然而,要解决这个问题并不是一个简单的事情,为了不显著牺牲数据库性能,长久以来人们对故障恢复机制进行了一系列的探索。
1981年,JIM GRAY等人在《The Recovery Manager of the System R Database Manager》中采用了一种非常直观的解决方式Shadow Paging[1]。System R的磁盘数据采用Page为最小的组织单位,一个File由多个Page组成,并通过称为Direcotry的元数据进行索引,每个Directory项纪录了当前文件的Page Table,指向其包含的所有Page。采用Shadow Paging的文件称为Shadow File,如下图中的File B所示,这种文件会包含两个Directory项,Current及Shadow。
事务对文件进行修改时,会获得新的Page,并加入Current的Page Table,所有的修改都只发生在Current Directory;事务Commit时,Current指向的Page刷盘,并通过原子的操作将Current的Page Table合并到Shadow Directory中,之后再返回应用Commit成功;事务Abort时只需要简单的丢弃Current指向的Page;如果过程中发生故障,只需要恢复Shadow Directory,相当于对所有未提交事务的回滚操作。Shadow Paging很方便的实现了:
虽然Shadow Paging设计简单直观,但它的一些缺点导致其并没有成为主流,首先,不支持Page内并发,一个Commit操作会导致其Page上所有事务的修改被提交,因此一个Page内只能包含一个事务的修改;其次,不断修改Page的物理位置,导致很难将相关的页维护在一起,破坏局部性;另外,对大事务而言,Commit过程在关键路径上修改Shadow Directory的开销可能很大,同时这个操作还必须保证原子;最后,增加了垃圾回收的负担,包括对失败事务的Current Pages和提交事务的Old Pages的回收。
由于传统磁盘顺序访问性能远好于随机访问,采用Logging的故障恢复机制意图利用顺序写的Log来记录对数据库的操作,并在故障恢复后通过Log内容将数据库恢复到正确的状态。简单的说,每次修改数据内容前先顺序写对应的Log,同时为了保证恢复时可以从Log中看到最新的数据库状态,要求Log先于数据内容落盘,也就是常说的Write Ahead Log,WAL。除此之外,事务完成Commit前还需要在Log中记录对应的Commit标记,以供恢复时了解当前的事务状态,因此还需要关注Commit标记和事务中数据内容的落盘顺序。根据Log中记录的内容可以分为三类:Undo-Only,Redo-Only,Redo-Undo。
Undo-Only Logging
Undo-Only Logging的Log记录可以表示未<T, X, v>,事务T修改了X的值,X的旧值是v。事务提交时,需要通过强制Flush保证Commit标记落盘前,对应事务的所有数据落盘,即落盘顺序为Log记录->Data->Commit标记。恢复时可以根据Commit标记判断事务的状态,并通过Undo Log中记录的旧值将未提交事务的修改回滚。我们来审视一下Undo-Only对Durability及Atomic的保证:
然而Undo-Only依然有不能Page内并发的问题,如果两个事务的修改落到一个Page中,一个事务提交前需要的强制Flush操作,会导致同Page所有事务的Data落盘,可能会早于对应的Log项从而损害WAL。同时,也会导致关键路径上过于频繁的磁盘随机访问。
Redo-Only Logging
不同于Undo-Only,采用Redo-Only的Log中记录的是修改后的新值。对应地,Commit时需要保证,Log中的Commit标记在事务的任何数据之前落盘,即落盘顺序为Log记录->Commit标记->Data。恢复时同样根据Commit标记判断事务状态,并通过Redo Log中记录的新值将已经Commit,但数据没有落盘的事务修改重放。
Redo-Only同样有不能Page内并发的问题,Page中的多个不同事务,只要有一个未提交就不能刷盘,这些数据全部都需要维护在内存中,造成较大的内存压力。
Redo-Undo Logging
可以看出的只有Undo或Redo的问题,主要来自于对Commit标记及Data落盘顺序的限制,而这种限制归根结底来源于Log信息中对新值或旧值的缺失。因此Redo-Undo采用同时记录新值和旧值的方式,来消除Commit和Data之间刷盘顺序的限制。
如此一来,同Page的不同事务提交就变得非常简单。同时可以将连续的数据攒着进行批量的刷盘已利用磁盘较高的顺序写性能。
从上面看出,Redo和Undo内容分别可以保证Durability和Atomic两个特性,其中一种信息的缺失需要用严格的刷盘顺序来弥补。这里关注的刷盘顺序包含两个维度:
总结一下,实现Durability可以通过记录Redo信息或要求Force刷盘顺序,实现Atomic需要记录Undo信息或要求No-Steal刷盘顺序,组合得到如下四种模式,如下图所示:
1992年,IBM的研究员们发表了《ARIES: a transaction recovery method supporting fine-granularity locking and partial rollbacks using write-ahead logging》[2],其中提出的ARIES逐步成为磁盘数据库实现故障恢复的标配,ARIES本质是一种Redo-Undo的WAL实现。 Normal过程:修改数据之前先追加Log记录,Log内容同时包括Redo和Undo信息,每个日志记录产生一个标记其在日志中位置的递增LSN(Log Sequence Number);数据Page中记录最后修改的日志项LSN,以此来判断Page中的内容的新旧程度,实现幂等。故障恢复阶段需要通过Log中的内容恢复数据库状态,为了减少恢复时需要处理的日志量,ARIES会在正常运行期间周期性的生成Checkpoint,Checkpoint中除了当前的日志LSN之外,还需要记录当前活跃事务的最新LSN,以及所有脏页,供恢复时决定重放Redo的开始位置。需要注意的是,由于生成Checkpoint时数据库还在正常提供服务(Fuzzy Checkpoint),其中记录的活跃事务及Dirty Page信息并不一定准确,因此需要Recovery阶段通过Log内容进行修正。
Recover过程:故障恢复包含三个阶段:Analysis,Redo和Undo。Analysis阶段的任务主要是利用Checkpoint及Log中的信息确认后续Redo和Undo阶段的操作范围,通过Log修正Checkpoint中记录的Dirty Page集合信息,并用其中涉及最小的LSN位置作为下一步Redo的开始位置RedoLSN。同时修正Checkpoint中记录的活跃事务集合(未提交事务),作为Undo过程的回滚对象;Redo阶段从Analysis获得的RedoLSN出发,重放所有的Log中的Redo内容,注意这里也包含了未Commit事务;最后Undo阶段对所有未提交事务利用Undo信息进行回滚,通过Log的PrevLSN可以顺序找到事务所有需要回滚的修改。
除此之外,ARIES还包含了许多优化设计,例如通过特殊的日志记录类型CLRs避免嵌套Recovery带来的日志膨胀,支持细粒度锁,并发Recovery等。[3]认为,ARIES有两个主要的设计目标:
从Shadow Paging到WAL,再到ARIES,一直围绕着两个主题:减少同步写以及尽量用顺序写代替随机写。而这些正是由于磁盘性能远小于内存,且磁盘顺序访问远好于随机访问。然而随着NVM磁盘的出现以及对其成为主流的预期,使得我们必须要重新审视我们所做的一切。相对于传统的HDD及SSD,NVM最大的优势在于:
在这种情况下,再来看ARIES的实现:
近年来,众多的研究尝试为NVM量身定制更合理的故障恢复机制,我们这里介绍其中两种比较有代表性的研究成果,MARS希望充分利用NVM并发及内部带宽的优势,将更多的任务交给硬件实现;而WBL则尝试重构当前的Log方式。
发表在2013年的SOSP上的《“From ARIES to MARS: Transaction support for next-generation, solid-state drives.” 》提出了一种尽量保留ARIES特性,但更适合NVM的故障恢复算法MARS[3]。MARS取消了Undo Log,保留的Redo Log也不同于传统的Append-Only,而是可以随机访问的。如下图所示,每个事务会占有一个唯一的TID,对应的Metadata中记录了Log和Data的位置
正常访问时,所有的数据修改都在对应的Redo Log中进行,不影响真实数据,由于没有Undo Log,需要采用No-Steal的方式,阻止Commit前的数据写回;Commit时会先设置事务状态为COMMITTED,之后利用NVM的内部带宽将Redo中的所有内容并发拷贝回Metadata中记录的数据位置。如果在COMMITED标记设置后发生故障,恢复时可以根据Redo Log中的内容重放。其本质是一种Redo加No-Steal的实现方式:
可以看出,MARS的Redo虽然称之为Log,但其实已经不同于传统意义上的顺序写文件,允许随机访问,更像是一种临时的存储空间,类似于Shadow的东西。之所以在Commit时进行数据拷贝而不是像Shadow Paging一样的元信息修改,笔者认为是为了保持数据的局部性,并且得益于硬件优异的内部带宽。
不同于MARS保留Redo信息的思路,2016年VLDB上的《 “Write Behind Logging” 》只保留了Undo信息。笔者认为这篇论文中关于WBL的介绍里,用大量笔墨介绍了算法上的优化部分,为了抓住其本质,这里先介绍最基本的WBL算法:WBL去掉了传统的Append Only的Redo和Undo日志,但仍然需要保留Undo信息用来回滚未提交事务。事务Commit前需要将其所有的修改强制刷盘,之后在Log中记录Commit标记,也就是这里说的Write Behind Log。恢复过程中通过分析Commit标记将为提交的事务通过Undo信息回滚。可以看出WBL算法本身非常简单,在这个基础上,WBL做了如下优化:
数据库故障恢复问题本质是要在容忍故障发生的情况下保证数据库的正确性,而这种正确性需要通过提供Durability of Updates和Failure Atomic来保证。其中Duribility of Update要保证Commit事务数据在恢复后存在,可以通过Force机制或者通过Redo信息回放来保证;对应的,Failure Atomic需要保证未Commit事务的修改再恢复后消失,可以通过No-Steal机制或者通过Undo信息回滚来保证。根据保证Durability和Atomic的不同方式,对本文提到的算法进行分类,如下:
本文介绍了磁盘数据库一路走来的核心发展思路,但距离真正的实现还有巨大的距离和众多的设计细节,如对Logical Log或Physical Log的选择、并发Recovery、Fuzzy Checkpoing、Nested Top Actions等,之后会用单独的文章以InnoDB为例来深入探究其设计和实现细节。
TL;DR
单线程串行执行命令
角度入手确保没有让Redis执行耗时长的命令,注意O(N)命令以及数量量大的O(logN)命令
比如keys,考虑用scan系列命令代替,迭代使用
针对这种要优化设计
不要把List当做列表使用,仅当做队列来使用
使用pipelining将连续执行的命令组合执行
操作系统的Transparent huge pages功能必须关闭:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
如果在虚拟机中运行Redis,可能天然就有虚拟机环境带来的固有延迟。可以通过./redis-cli –intrinsic-latency 100命令查看固有延迟。同时如果对Redis的性能有较高要求的话,应尽可能在物理机上直接部署Redis。
检查数据持久化策略
考虑引入读写分离机制
检查慢日志,slowlog命令
配置项
slowlog-log-slower-than xxxms #执行时间慢于xxx毫秒的命令计入Slow Log slowlog-max-len xxx
Redis的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:
当Linux将Redis所用的内存分页移至swap空间时,将会阻塞Redis进程,导致Redis出现不正常的延迟。Swap通常在物理内存不足或一些进程在进行大量I/O操作时发生,应尽可能避免上述两种情况的出现。
/proc/
当同一秒内有大量key过期时,也会引发Redis的延迟。在使用时应尽量将key的失效时间错开。
随机失效时间防止雪崩也是最佳实践了
Redis的主从复制能力可以实现一主多从的多节点架构,在这一架构下,主节点接收所有写请求,并将数据同步给多个从节点。 在这一基础上,我们可以让从节点提供对实时性要求不高的读请求服务,以减小主节点的压力。
尤其是针对一些使用了长耗时命令的统计类任务,完全可以指定在一个或多个从节点上执行,避免这些长耗时命令影响其他请求的响应。
不只是time_wait, 说说tcp关闭状态
几个问题
Redis采用Key-Value型的基本数据结构,任何二进制序列都可以作为Redis的Key使用(例如普通的字符串或一张JPEG图片) 关于Key的一些注意事项:
String是Redis的基础数据类型,Redis没有Int、Float、Boolean等数据类型的概念,所有的基本类型在Redis中都以String体现。
与String相关的常用命令:
上文提到过,Redis的基本数据类型只有String,但Redis可以把String作为整型或浮点型数字来使用,主要体现在INCR、DECR类的命令上:
INCR/DECR系列命令要求操作的value类型为String,并可以转换为64位带符号的整型数字,否则会返回错误。 也就是说,进行INCR/DECR系列命令的value,必须在[-2^63 ~ 2^63 - 1]范围内。
前文提到过,Redis采用单线程模型,天然是线程安全的,这使得INCR/DECR命令可以非常便利的实现高并发场景下的精确控制。
在高并发场景下实现库存余量的精准校验,确保不出现超卖的情况。
设置库存总量:
SET inv:remain "100"
库存扣减+余量校验:
DECR inv:remain
当DECR命令返回值大于等于0时,说明库存余量校验通过,如果返回小于0的值,则说明库存已耗尽。
假设同时有300个并发请求进行库存扣减,Redis能够确保这300个请求分别得到99到-200的返回值,每个请求得到的返回值都是唯一的,绝对不会找出现两个请求得到一样的返回值的情况。
实现类似于RDBMS的Sequence功能,生成一系列唯一的序列号
设置序列起始值:
SET sequence "10000"
获取一个序列值:
INCR sequence
直接将返回值作为序列使用即可。
获取一批(如100个)序列值:
INCRBY sequence 100
假设返回值为N,那么[N - 99 ~ N]的数值都是可用的序列值。
当多个客户端同时向Redis申请自增序列时,Redis能够确保每个客户端得到的序列值或序列范围都是全局唯一的,绝对不会出现不同客户端得到了重复的序列值的情况。
Redis的List是链表型的数据结构,可以使用LPUSH/RPUSH/LPOP/RPOP等命令在List的两端执行插入元素和弹出元素的操作。虽然List也支持在特定index上插入和读取元素的功能,但其时间复杂度较高(O(N)),应小心使用。
与List相关的常用命令:
应谨慎使用的List相关命令:
由于Redis的List是链表结构的,上述的三个命令的算法效率较低,需要对List进行遍历,命令的耗时无法预估,在List长度大的情况下耗时会明显增加,应谨慎使用。
换句话说,Redis的List实际是设计来用于实现队列,而不是用于实现类似ArrayList这样的列表的。如果你不是想要实现一个双端出入的队列,那么请尽量不要使用Redis的List数据结构。
为了更好支持队列的特性,Redis还提供了一系列阻塞式的操作命令,如BLPOP/BRPOP等,能够实现类似于BlockingQueue的能力,即在List为空时,阻塞该连接,直到List中有对象可以出队时再返回。针对阻塞类的命令,此处不做详细探讨,请参考官方文档(https://redis.io/topics/data-types-intro) 中”Blocking operations on lists”一节。
Hash即哈希表,Redis的Hash和传统的哈希表一样,是一种field-value型的数据结构,可以理解成将HashMap搬入Redis。 Hash非常适合用于表现对象类型的数据,用Hash中的field对应对象的field即可。 Hash的优点包括:
与Hash相关的常用命令:
应谨慎使用的Hash相关命令:
上述三个命令都会对Hash进行完整遍历,Hash中的field数量与命令的耗时线性相关,对于尺寸不可预知的Hash,应严格避免使用上面三个命令,而改为使用HSCAN命令进行游标式的遍历,具体请见 https://redis.io/commands/scan
Redis Set是无序的,不可重复的String集合。
与Set相关的常用命令:
慎用的Set相关命令:
上述几个命令涉及的计算量大,应谨慎使用,特别是在参与计算的Set尺寸不可知的情况下,应严格避免使用。可以考虑通过SSCAN命令遍历获取相关Set的全部member(具体请见 https://redis.io/commands/scan ),如果需要做并集/交集/差集计算,可以在客户端进行,或在不服务实时查询请求的Slave上进行。
Redis Sorted Set是有序的、不可重复的String集合。Sorted Set中的每个元素都需要指派一个分数(score),Sorted Set会根据score对元素进行升序排序。如果多个member拥有相同的score,则以字典序进行升序排序。
Sorted Set非常适合用于实现排名。
Sorted Set的主要命令:
慎用的Sorted Set相关命令:
上述几个命令,应尽量避免传递[0 -1]或[-inf +inf]这样的参数,来对Sorted Set做一次性的完整遍历,特别是在Sorted Set的尺寸不可预知的情况下。可以通过ZSCAN命令来进行游标式的遍历(具体请见 https://redis.io/commands/scan ),或通过LIMIT参数来限制返回member的数量(适用于ZRANGEBYSCORE和ZREVRANGEBYSCORE命令),以实现游标式的遍历。
Redis的这两种数据结构相较之前的并不常用,在本文中只做简要介绍,如想要详细了解这两种数据结构与其相关的命令,请参考官方文档https://redis.io/topics/data-types-intro 中的相关章节
Bitmap在Redis中不是一种实际的数据类型,而是一种将String作为Bitmap使用的方法。可以理解为将String转换为bit数组。使用Bitmap来存储true/false类型的简单数据极为节省空间。
HyperLogLogs是一种主要用于数量统计的数据结构,它和Set类似,维护一个不可重复的String集合,但是HyperLogLogs并不维护具体的member内容,只维护member的个数。也就是说,HyperLogLogs只能用于计算一个集合中不重复的元素数量,所以它比Set要节省很多内存空间。
Redis提供了将数据定期自动持久化至硬盘的能力,包括RDB和AOF两种方案,两种方案分别有其长处和短板,可以配合起来同时运行,确保数据的稳定性。
Redis的数据持久化机制是可以关闭的。如果你只把Redis作为缓存服务使用,Redis中存储的所有数据都不是该数据的主体而仅仅是同步过来的备份,那么可以关闭Redis的数据持久化机制。 但通常来说,仍然建议至少开启RDB方式的数据持久化,因为:
采用RDB持久方式,Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。可以在配置文件中配置Redis进行快照保存的时机:
save [seconds] [changes]
意为在[seconds]秒内如果发生了[changes]次数据修改,则进行一次RDB快照保存,例如
save 60 100
会让Redis每60秒检查一次数据变更情况,如果发生了100次或以上的数据变更,则进行RDB快照保存。 可以配置多条save指令,让Redis执行多级的快照保存策略。 Redis默认开启RDB快照,默认的RDB策略如下:
save 900 1
save 300 10
save 60 10000
也可以通过BGSAVE命令手工触发RDB快照保存。
RDB的优点:
RDB的缺点:
采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。
AOF默认是关闭的,如要开启,进行如下配置:
appendonly yes
AOF提供了三种fsync配置,always/everysec/no,通过配置项[appendfsync]指定:
随着AOF不断地记录写操作日志,必定会出现一些无用的日志,例如某个时间点执行了命令SET key1 “abc”,在之后某个时间点又执行了SET key1 “bcd”,那么第一条命令很显然是没有用的。大量的无用日志会让AOF文件过大,也会让数据恢复的时间过长。 所以Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集。 AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
上面两行配置的含义是,Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite。同时如果增长的大小没有达到64mb,则不会进行rewrite。
AOF的优点:
AOF的缺点:
默认情况下,在32位OS中,Redis最大使用3GB的内存,在64位OS中则没有限制。
在使用Redis时,应该对数据占用的最大空间有一个基本准确的预估,并为Redis设定最大使用的内存。否则在64位OS中Redis会无限制地占用内存(当物理内存被占满后会使用swap空间),容易引发各种各样的问题。
通过如下配置控制Redis使用的最大内存:
maxmemory 100mb
在内存占用达到了maxmemory后,再向Redis写入数据时,Redis会:
在为Redis设置maxmemory时,需要注意:
Redis提供了5种数据淘汰策略:
最好为Redis指定一种有效的数据淘汰策略以配合maxmemory设置,避免在内存使用满后发生写入失败的情况。
一般来说,推荐使用的策略是volatile-lru,并辨识Redis中保存的数据的重要性。对于那些重要的,绝对不能丢弃的数据(如配置类数据等),应不设置有效期,这样Redis就永远不会淘汰这些数据。对于那些相对不是那么重要的,并且能够热加载的数据(比如缓存最近登录的用户信息,当在Redis中找不到时,程序会去DB中读取),可以设置上有效期,这样在内存不够时Redis就会淘汰这部分数据。
配置方法:
maxmemory-policy volatile-lru #默认是noeviction,即不进行数据淘汰
Redis提供许多批量操作的命令,如MSET/MGET/HMSET/HMGET等等,这些命令存在的意义是减少维护网络连接和传输数据所消耗的资源和时间。 例如连续使用5次SET命令设置5个不同的key,比起使用一次MSET命令设置5个不同的key,效果是一样的,但前者会消耗更多的RTT(Round Trip Time)时长,永远应优先使用后者。
然而,如果客户端要连续执行的多次操作无法通过Redis命令组合在一起,例如:
SET a "abc"
INCR b
HSET c name "hi"
此时便可以使用Redis提供的pipelining功能来实现在一次交互中执行多条命令。 使用pipelining时,只需要从客户端一次向Redis发送多条命令(以\r\n)分隔,Redis就会依次执行这些命令,并且把每个命令的返回按顺序组装在一起一次返回,比如:
$ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
大部分的Redis客户端都对Pipelining提供支持,所以开发者通常并不需要自己手工拼装命令列表。
Pipelining只能用于执行连续且无相关性的命令,当某个命令的生成需要依赖于前一个命令的返回时,就无法使用Pipelining了。
通过Scripting功能,可以规避这一局限性
Pipelining能够让Redis在一次交互中处理多条命令,然而在一些场景下,我们可能需要在此基础上确保这一组命令是连续执行的。
比如获取当前累计的PV数并将其清0
> GET vCount
12384
> SET vCount 0
OK
如果在GET和SET命令之间插进来一个INCR vCount,就会使客户端拿到的vCount不准确。
Redis的事务可以确保复数命令执行时的原子性。也就是说Redis能够保证:一个事务中的一组命令是绝对连续执行的,在这些命令执行完成之前,绝对不会有来自于其他连接的其他命令插进去执行。
通过MULTI和EXEC命令来把这两个命令加入一个事务中:
> MULTI
OK
> GET vCount
QUEUED
> SET vCount 0
QUEUED
> EXEC
1) 12384
2) OK
Redis在接收到MULTI命令后便会开启一个事务,这之后的所有读写命令都会保存在队列中但并不执行,直到接收到EXEC命令后,Redis会把队列中的所有命令连续顺序执行,并以数组形式返回每个命令的返回结果。
可以使用DISCARD命令放弃当前的事务,将保存的命令队列清空。
需要注意的是,Redis事务不支持回滚: 如果一个事务中的命令出现了语法错误,大部分客户端驱动会返回错误,2.6.5版本以上的Redis也会在执行EXEC时检查队列中的命令是否存在语法错误,如果存在,则会自动放弃事务并返回错误。 但如果一个事务中的命令有非语法类的错误(比如对String执行HSET操作),无论客户端驱动还是Redis都无法在真正执行这条命令之前发现,所以事务中的所有命令仍然会被依次执行。在这种情况下,会出现一个事务中部分命令成功部分命令失败的情况,然而与RDBMS不同,Redis不提供事务回滚的功能,所以只能通过其他方法进行数据的回滚。
Redis提供了WATCH命令与事务搭配使用,实现CAS乐观锁的机制。
假设要实现将某个商品的状态改为已售:
if(exec(HGET stock:1001 state) == "in stock")
exec(HSET stock:1001 state "sold");
这一伪代码执行时,无法确保并发安全性,有可能多个客户端都获取到了”in stock”的状态,导致一个库存被售卖多次。
使用WATCH命令和事务可以解决这一问题:
exec(WATCH stock:1001);
if(exec(HGET stock:1001 state) == "in stock") {
exec(MULTI);
exec(HSET stock:1001 state "sold");
exec(EXEC);
}
WATCH的机制是:在事务EXEC命令执行时,Redis会检查被WATCH的key,只有被WATCH的key从WATCH起始时至今没有发生过变更,EXEC才会被执行。如果WATCH的key在WATCH命令到EXEC命令之间发生过变化,则EXEC命令会返回失败。
通过EVAL与EVALSHA命令,可以让Redis执行LUA脚本。这就类似于RDBMS的存储过程一样,可以把客户端与Redis之间密集的读/写交互放在服务端进行,避免过多的数据交互,提升性能。
Scripting功能是作为事务功能的替代者诞生的,事务提供的所有能力Scripting都可以做到。Redis官方推荐使用LUA Script来代替事务,前者的效率和便利性都超过了事务。
关于Scripting的具体使用,本文不做详细介绍,请参考官方文档 https://redis.io/commands/eval
首先把reactor和proactor流程图抄过来
反应器Reactor Reactor模式结构
Reactor包含如下角色:
Handle 句柄;用来标识socket连接或是打开文件;
Synchronous Event Demultiplexer:同步事件多路分解器:由操作系统内核实现的一个函数;用于阻塞等待发生在句柄集合上的一个或多个事件;(如select/epoll;)
Event Handler:事件处理接口
Concrete Event HandlerA:实现应用程序所提供的特定事件处理逻辑;
Reactor:反应器,定义一个接口,实现以下功能:
1)供应用程序注册和删除关注的事件句柄;
2)运行事件循环;
3)有就绪事件到来时,分发事件到之前注册的回调函数上处理;
“反应”器名字中”反应“的由来: “反应”即“倒置”,“控制逆转”,具体事件处理程序不调用反应器,而是由反应器分配一个具体事件处理程序,具体事件处理程序对某个指定的事件发生做出反应;这种控制逆转又称为“好莱坞法则”(不要调用我,让我来调用你) 业务流程及时序图
应用启动,将关注的事件handle注册到Reactor中;
调用Reactor,进入无限事件循环,等待注册的事件到来;
事件到来,select返回,Reactor将事件分发到之前注册的回调函数中处理;
主动器Proactor Proactor模式结构
Proactor主动器模式包含如下角色
Handle 句柄;用来标识socket连接或是打开文件;
Asynchronous Operation Processor:异步操作处理器;负责执行异步操作,一般由操作系统内核实现;
Asynchronous Operation:异步操作
Completion Event Queue:完成事件队列;异步操作完成的结果放到队列中等待后续使用
Proactor:主动器;为应用程序进程提供事件循环;从完成事件队列中取出异步操作的结果,分发调用相应的后续处理逻辑;
Completion Handler:完成事件接口;一般是由回调函数组成的接口;
Concrete Completion Handler:完成事件处理逻辑;实现接口定义特定的应用处理逻辑;
业务流程及时序图
应用程序启动,调用异步操作处理器提供的异步操作接口函数,调用之后应用程序和异步操作处理就独立运行;应用程序可以调用新的异步操作,而其它操作可以并发进行;
应用程序启动Proactor主动器,进行无限的事件循环,等待完成事件到来;
异步操作处理器执行异步操作,完成后将结果放入到完成事件队列;
主动器从完成事件队列中取出结果,分发到相应的完成事件回调函数处理逻辑中;
对比两者的区别 主动和被动
以主动写为例: Reactor将handle放到select(),等待可写就绪,然后调用write()写入数据;写完处理后续逻辑; Proactor调用aoi_write后立刻返回,由内核负责写操作,写完后调用相应的回调函数处理后续逻辑;
可以看出,Reactor被动的等待指示事件的到来并做出反应;它有一个等待的过程,做什么都要先放入到监听事件集合中等待handler可用时再进行操作; Proactor直接调用异步读写操作,调用完后立刻返回; 实现
Reactor实现了一个被动的事件分离和分发模型,服务等待请求事件的到来,再通过不受间断的同步处理事件,从而做出反应;
Proactor实现了一个主动的事件分离和分发模型;这种设计允许多个任务并发的执行,从而提高吞吐量;并可执行耗时长的任务(各个任务间互不影响) 优点
Reactor实现相对简单,对于耗时短的处理场景处理高效; 操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性; 事件的串行化对应用是透明的,可以顺序的同步执行而不需要加锁; 事务分离:将与应用无关的多路分解和分配机制和与应用相关的回调函数分离开来,
Proactor性能更高,能够处理耗时长的并发场景; 缺点
Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;
Proactor实现逻辑复杂;依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少,实现优秀的如windows IOCP,但由于其windows系统用于服务器的局限性,目前应用范围较小;而Unix/Linux系统对纯异步的支持有限,应用事件驱动的主流还是通过select/epoll来实现; 适用场景
Reactor:同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序;
Proactor:异步接收和同时处理多个服务请求的事件驱动程序