MIT6.824 kv-raft
使用lab2构建的raft库,提供一个fault-tolerance的key-value服务
完整代码:link
lab3A
Client轮询发送RPC请求,直到找到是leader的server。
Server接受Client的请求,构造Op日志,交给raft节点,raft节点集群之间达成共识后,每个raft节点通过applyCh告诉相应的server,server将日志的命令应用到状态机上,leader server将结果返回给client。
难点
当存在多个client同时请求的时候,有多个日志被提交,leader如何定位状态机的应用结果返回给哪个client?解决:用每个命令提交时的log index作为索引,映射到一个返回结果的通道,对于applyCh返回来的日志,根据日志index确定要通知的client。
如何实现线性化语义?解决:给每一个请求一个UniqueID(Increasing),server保存记录目前已经被应用到状态机最大的请求ID,防止请求被多次应用(写请求)。对于读请求,总让client看到最新的状态。client发送RPC请求的时候,附带clientID和commandId,共同构成这个uniqueID。
需要注意的点
发送RPC的for循环中需要使用一个新的reply结构体,传递给rpc,否则还是之前的reply结果。
server中,只有leader需要在RPC的等待channel上发送返回response。
运行结果
$ go test -run 3A
Test: one client (3A) ...
... Passed -- 15.7 5 396 74
Test: many clients (3A) ...
... Passed -- 18.1 5 765 365
Test: unreliable net, many clients (3A) ...
... Passed -- 24.0 5 959 179
Test: concurrent append to same key, unreliable (3A) ...
... Passed -- 6.4 3 150 52
Test: progress in majority (3A) ...
... Passed -- 1.5 5 53 2
Test: no progress in minority (3A) ...
... Passed -- 1.6 5 77 3
Test: completion after heal (3A) ...
... Passed -- 1.2 5 41 3
Test: partitions, one client (3A) ...
... Passed -- 24.1 5 621 46
Test: partitions, many clients (3A) ...
... Passed -- 25.5 5 1019 220
Test: restarts, one client (3A) ...
labgob warning: Decoding into a non-default variable/field int may not work
... Passed -- 22.9 5 1096 70
Test: restarts, many clients (3A) ...
... Passed -- 25.0 5 1733 375
Test: unreliable net, restarts, many clients (3A) ...
... Passed -- 30.1 5 2015 151
Test: restarts, partitions, many clients (3A) ...
... Passed -- 31.9 5 1534 159
Test: unreliable net, restarts, partitions, many clients (3A) ...
... Passed -- 33.1 5 1775 97
Test: unreliable net, restarts, partitions, many clients, linearizability checks (3A) ...
... Passed -- 32.7 7 4532 293
PASS
ok _/Users/sjy/develop/Go/6.824/src/kvraft 296.354s
Lab3B Key/value service with log compaction
为了防止raft的log无限制增长浪费空间,kvserver需要在适当的时候告诉raft进行日志压缩,并主动保存状态机的相关状态,保存到persister的snapshot中。raft的leader节点也要检测follower的log是否过于过时,如果follower对于回应AppendEntry RPC的回复出现请求的日志条目已经被snapshot了,leader需要发送installSnapshot RPC,更新follower的Snapshot。
为了实现动态的日志,给Raft结构增加一个startIndex,代表了日志条目逻辑下标的开始下标,之前一直默认是1。当进行日志压缩后,startIndex增加,需要给Raft定义新的日志操作接口,来完成逻辑下标到真实下标的转换。startIndex也要作为需要persist的持久状态(否则就不能通过TestSnapshotUnreliableRecoverConcurrentPartitionLinearizable3B)。
当kvserver和raft重启的时候,需要读取相关的state或者snapshot,来获得崩溃之前的状态(如状态机等)。
对于不是leader的kvserver,相应的raft节点可能收到leader的InstallSnapshot RPC,此时raft节点需要通过applyCh来告诉kvserver来更新状态机。
运行结果
$ go test -run 3B
Test: InstallSnapshot RPC (3B) ...
... Passed -- 18.8 3 409 63
Test: snapshot size is reasonable (3B) ...
--- FAIL: TestSnapshotSize3B (161.48s)
config.go:65: test took longer than 120 seconds
Test: restarts, snapshots, one client (3B) ...
labgob warning: Decoding into a non-default variable/field int may not work
... Passed -- 22.8 5 1111 74
Test: restarts, snapshots, many clients (3B) ...
... Passed -- 35.2 5 4156 1480
Test: unreliable net, snapshots, many clients (3B) ...
... Passed -- 23.6 5 933 189
Test: unreliable net, restarts, snapshots, many clients (3B) ...
... Passed -- 27.0 5 1860 197
Test: unreliable net, restarts, partitions, snapshots, many clients (3B) ...
... Passed -- 35.4 5 1807 89
Test: unreliable net, restarts, partitions, snapshots, many clients, linearizability checks (3B) ...
... Passed -- 34.2 7 4541 258
FAIL
exit status 1
发现TestSnapshotSize3B总是超时,要求在120s内完成,但是我的实现总是160s。一开始以为是我的raft层没有实现好,于是又去参考了vtta的raft进行实现,返现速度仍然是160s。尬住了