2C——状态持久化(Persistence)
lab 2C是lab 2四个子实验中最简单的了,就是需要学生把论文中提到的需要持久化的状态保存起来,在raft节点宕机恢复之后可以读取,具体来说,需要持久化的字段有:
log[]
日志记录
currentTerm
当前任期
votedFor
当前任期选举时投票给了谁
而leader的nextIndex[]
以及matchIndex
则可以通过恢复后想follower节点发送AppendEntriesRPC来恢复。然后在做2D日志快照的时候,还有新的字段需要被持久化,接下来就说一下2D——日志快照。(2C确实简单)
2D——日志快照(Snapshot)
lab 2D需要学生实现论文中提到的快照功能:由于在长时间运行raft后,raft的log可能会变得非常长而效率逐渐下滑,且follower节点假如落后很多,将需要很长时间来恢复日志,最后整个集群就会慢到不可用的地步。日志快照就是使用raft的应用对自身的当前状态进行一个快照,然后通知raft可以丢弃快照前的所有log,以此来定期缩短日志,来保持日志的长度在可用范围内。
实际上,实现快照功能需要重写很多逻辑,这次lab也是lab2中花费我最多时间的,后面发现2020年robert morris授课的那学期根本就没有2D,实际上是lab3的内容😂
比较重要的改动是:
- 2C持久化需要把快照中最后一个日志的任期和index也存起来
- 假如follower太落后以至其
nextIndex[server]
小于leader快照后的第一个index,也就是leader已经没有它需要的log了,此时需要我们实现一个InstallSnapShotRPC
,把整个snapshot发给follower,直接更新到可以发log的地方。
具体代码就不展示了,写到后面还是用了好多锁,总感觉不够优雅,中途常常遇到各种race的问题,比如install snapshot的时候又接受append entry,或者同时又apply导致各种奇奇怪怪的现象,最后恍然大悟把install snapshot和apply串行化,问题一下子就少了好多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func (rf *Raft) applyLog() {
timer := time.NewTicker(10 * time.Millisecond)
defer timer.Stop()
for !rf.killed() {
select {
case <-timer.C:
rf.doApply()
case <-rf.applyNotify:
rf.doApply()
// installSnapshotRPC不能跟背景apply一起跑
case msg := <-rf.snapCh:
rf.doSnapshot(msg)
}
}
}
|
lab2测试结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
midknight@DESKTOP-9G893VB:~/6.5840/src/raft$ time go test -race
Test (2A): initial election ...
... Passed -- 3.6 3 62 17900 0
Test (2A): election after network failure ...
... Passed -- 5.5 3 402 80798 0
Test (2A): multiple elections ...
... Passed -- 5.6 7 1271 261223 0
Test (2B): basic agreement ...
... Passed -- 0.9 3 18 4898 3
Test (2B): RPC byte count ...
... Passed -- 1.9 3 48 114584 11
Test (2B): test progressive failure of followers ...
... Passed -- 4.9 3 266 60216 3
Test (2B): test failure of leaders ...
... Passed -- 5.4 3 531 117466 3
Test (2B): agreement after follower reconnects ...
... Passed -- 5.9 3 164 43728 7
Test (2B): no agreement if too many followers disconnect ...
... Passed -- 3.8 5 406 91150 3
Test (2B): concurrent Start()s ...
... Passed -- 1.1 3 18 4898 6
Test (2B): rejoin of partitioned leader ...
... Passed -- 5.4 3 570 131751 4
Test (2B): leader backs up quickly over incorrect follower logs ...
... Passed -- 18.5 5 3061 2601824 102
Test (2B): RPC counts aren't too high ...
... Passed -- 2.2 3 34 10178 12
Test (2C): basic persistence ...
... Passed -- 5.0 3 199 45967 6
Test (2C): more persistence ...
... Passed -- 17.1 5 2315 511231 16
Test (2C): partitioned leader and one follower crash, leader restarts ...
... Passed -- 2.2 3 54 13771 4
Test (2C): Figure 8 ...
... Passed -- 31.8 5 1600 340106 29
Test (2C): unreliable agreement ...
... Passed -- 2.1 5 426 158046 246
Test (2C): Figure 8 (unreliable) ...
... Passed -- 35.3 5 9070 19474489 84
Test (2C): churn ...
... Passed -- 16.6 5 4974 9807901 1343
Test (2C): unreliable churn ...
... Passed -- 16.6 5 3642 3985790 776
Test (2D): snapshots basic ...
... Passed -- 5.0 3 138 50814 202
Test (2D): install snapshots (disconnect) ...
... Passed -- 46.1 3 2399 1524295 356
Test (2D): install snapshots (disconnect+unreliable) ...
... Passed -- 54.3 3 3084 1604349 322
Test (2D): install snapshots (crash) ...
... Passed -- 35.3 3 1202 1008637 321
Test (2D): install snapshots (unreliable+crash) ...
... Passed -- 41.9 3 1723 1352002 316
Test (2D): crash and restart all servers ...
... Passed -- 8.3 3 184 55156 40
Test (2D): snapshot initialization after crash ...
... Passed -- 3.4 3 68 20042 14
PASS
ok 6.5840/raft 385.480s
real 6m27.107s
user 1m6.255s
sys 0m10.445s
|
在2023 lab网页上,提到了lab2所有测试加起来的大概总耗时:
Hint: A reasonable amount of time to consume for the full set of Lab 2 tests (2A+2B+2C+2D) without -race is 6 minutes of real time and one minute of CPU time. When running with -race, it is about 10 minutes of real time and two minutes of CPU time.
在我的实现中,带race和不带race的耗时跟上面的测试结果时间差别不大,6分钟左右+1分钟用户时间,估计新版本的go中race detector做了优化。