前言
花两天把lab3B/C写了一下,有了A的基础,简单了不少。gitee地址放在末尾。
一、3B/3C 前的整体认知
1.1 3B 的目标
- Leader 接收
Start(command)→ 追加到rf.logs→ 复制到多数派 → 推进commitIndex→ 通过applyChan交给状态机。
1.2 3C 的目标
- 把 term / votedFor / logs 做到"随时可重启且不违背承诺"(同一任期不可能投两次票、日志不可能倒退)。
二、Part B:日志复制(Log Replication,3B)
2.1 整体思路:把 Raft 看成一条闭环流水线
主要流程:Start 写入 → AppendEntries 复制 → reply 反馈 → Leader 推进 commit → 心跳携带 LeaderCommit → 双端 applier 应用。
-
客户端 → Leader:Start
-
Leader → Followers:AppendEntries(entries, LeaderCommit)
-
Followers → Leader:AppendEntriesReply(Success/Term)
-
Leader:更新 nextIndex/matchIndex → updateCommitIndex → 唤醒 applier
-
Followers:更新 commitIndex → 唤醒 applier
2.2 关键数据结构
-
持久化状态(3C 才真正持久化) :
rf.term、rf.votedFor、rf.logs -
易失状态(所有节点) :
rf.commitIndex、rf.lastApplied -
易失状态(Leader 专用) :
rf.nextIndex[]、rf.matchIndex[]
日志索引约定:
-
rf.logs[0]为占位条目(term=0, command=nil) 不仅能够对日志进行更方便的访问,在3D中这里放快照的最后一个日志信息 -
第一条真实日志在 index=1
-
最后一条 index =
len(rf.logs)-1
2.3 Start(command):
写这一段时建议按(接口语义 + 并发点 + 返回值)组织:
-
语义 :只有
rf.state==Leader才能接收命令;否则立即返回(-1, rf.term, false) -
写入 :追加
LogEntry{Term: rf.term, Command: command}到rf.logs -
返回 :
(index, term, true) -
触发复制:两种常见方式
-
触发一次广播(推模式),或
-
等下一次心跳(拉模式,简单但可能慢)
bash
func (rf *Raft) Start(command interface{}) (int, int, bool) {
// Your code here (3B).
rf.mu.Lock()
if rf.Killed() {
rf.mu.Unlock()
return -1, -1, false
}
if rf.state != Leader {
rf.mu.Unlock()
return -1, -1, false
}
term := rf.currentTerm
index := rf.getLastIndex() + 1
rf.logs = append(rf.logs, LogEntry{Term: term, Command: command})
rf.persist()
rf.mu.Unlock()
//Leader 刚追加了一条日志,立刻再推一轮 RPC,不用干等下面 ticker 的 50ms 心跳周期,复制会快一点
go rf.broadcastHeartbeat()//广播心跳
return index, term, true
}
2.4 AppendEntries(Follower 侧):日志一致性检查与截断追加
这一节建议用"检查顺序"写,跟 Figure 2/论文一致,最好对着一步步走:
-
term 检查 :
args.Term < rf.term直接拒绝,回reply.Term = rf.term -
退位与重置计时器 :收到合法 Leader 的 RPC →
lastHeartBeatTime = now,必要时退回 Follower -
一致性检查 :用
PrevLogIndex/PrevLogTerm验证日志前缀一致
-
本地太短(PrevLogIndex 超界)→
Success=false -
term 不匹配 →
Success=false
-
截断并追加 :从
PrevLogIndex+1开始,删除本地冲突后缀,再追加Entries -
推进 commitIndex :
commitIndex = min(args.LeaderCommit, lastNewIndex)
Go
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
DPrintf("[%d] AppendEntries from L%d term=%d, my term=%d state=%d", rf.me, args.LeaderId, args.Term, rf.currentTerm, rf.state)
reply.Success = false
reply.Term = rf.currentTerm
// 发现任期比自己小
if args.Term < rf.currentTerm {
DPrintf("[%d] AppendEntries: 拒绝 term 更小 leader=%d argsTerm=%d myTerm=%d", rf.me, args.LeaderId, args.Term, rf.currentTerm)
return
}
// 发现更高任期或来自合法 Leader 的心跳
rf.lastHeartBeatTime = time.Now()
if args.Term > rf.currentTerm {
rf.currentTerm = args.Term
rf.votedFor = -1
rf.persist()
DPrintf("[%d] step down to Follower due to AE term=%d", rf.me, rf.currentTerm)
}
rf.state = Follower
//一致性检查
//日志长度不够 Follower 若还没有 PrevLogIndex 这一条,才拒绝
if rf.getLastIndex() < args.PrevLogIndex { // 3C
reply.ConflictTerm = -1 // 3C
reply.ConflictIndex = rf.getLastIndex() + 1 // 3C
return // 3C
}
//一致性检查:PrevLogIndex 处的 Term 是否匹配
// 特别注意:如果 PrevLogIndex 恰好在快照边界,要用 LastIncludedIndex
if args.PrevLogIndex < rf.LastIncludedIndex { // 3C
reply.ConflictTerm = -1 // 3C
reply.ConflictIndex = rf.LastIncludedIndex + 1 // 3C
return // 3C
}
//PrevLogIndex处任期不匹配
if rf.getTermByIndex(args.PrevLogIndex) != args.PrevLogTerm { // 3C
reply.ConflictTerm = rf.getTermByIndex(args.PrevLogIndex) // 3C
idx := args.PrevLogIndex // 3C
//从最新的日志位置开始向前找,找到冲突任期的下标
//告诉leader,这个下标是冲突任期的下标,下一步继续找冲突位置,若没有则进行同步
for idx > rf.LastIncludedIndex && rf.getTermByIndex(idx) == reply.ConflictTerm { // 3C
idx-- // 3C
}
reply.ConflictIndex = idx + 1 // 3C
return // 3C
}
//追加日志
isChange := false // 3D
for i, entry := range args.Entries { // 3D
logicIdx := i + args.PrevLogIndex + 1 // 3D
// 如果 logicIdx 已经落入快照范围,跳过(或者报错,理论上不该发生)
if logicIdx <= rf.LastIncludedIndex { // 3D
continue // 3D
}
phyIdx := rf.getPhysicIdx(logicIdx) // 3D
if phyIdx < len(rf.logs) { // 3D
//如果索引范围内已经有日志了,检查任期
if rf.logs[phyIdx].Term != entry.Term { // 3D
//如果追加日志的位置的任期和leader日志的位置的任期不相等
//将idx下标前面的日志进行切片保留
rf.logs = rf.logs[:phyIdx] // 3D
rf.logs = append(rf.logs, entry) // 3D
isChange = true // 3D
}
//如果任期一样,说明这一段已经同步过了,下一条
} else { // 3D
//超出本地的日志长度,直接追加
rf.logs = append(rf.logs, entry) // 3D
isChange = true // 3D
}
}
if isChange { // 3D
rf.persist() // 3D
}
// 更新 CommitIndex:须用「当前日志最后一条」与 LeaderCommit 取 min。
// 心跳时 len(Entries)==0,若仍用 PrevLogIndex+0 会小于 getLastIndex(),导致 commit 永远追不上 Leader。
if args.LeaderCommit > rf.commitIndex {
rf.commitIndex = min(args.LeaderCommit, rf.getLastIndex())
rf.applyCond.Broadcast()
}
reply.Success = true
}
2.5 Leader 侧复制:nextIndex / matchIndex 维护
这一部分主要分为两个小点:
- 发送时构造 args(按 peer 单独算)
-
prevIdx := nextIndex[i]-1 -
entries := logs[nextIndex[i]:] 将日志切片 -
心跳就是
entries为空,但仍携带LeaderCommit
- 收到回复时更新
- 新增关键成员ConflictTerm (冲突任期)/ ConflictIndex(冲突任期索引)
-
Success=true:更新matchIndex[i]、nextIndex[i] -
Success=false:回退nextIndex[i]并重试 -
优化(性能关键):ConflictTerm / ConflictIndex,让回退"整块跳过"
Go
func (rf *Raft) sendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool {
ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)
if ok {
rf.mu.Lock()
defer rf.mu.Unlock()
// 处理回复(任期更新、调整 nextIndex 等)
if reply.Term > rf.currentTerm {
rf.currentTerm = reply.Term
rf.state = Follower
rf.votedFor = -1
rf.persist()
return ok
}
// 2. 状态检查
if rf.state != Leader || rf.currentTerm != args.Term {
return ok
}
// 3B 以后你会在这里处理日志同步的 reply.Success 为 false 的情况
// 如果追加日志成功 ------ 对应论文 Leader 规则:If successful, update matchIndex and nextIndex
if reply.Success {
newMathIdx := args.PrevLogIndex + len(args.Entries)
if newMathIdx > rf.matchIndex[server] {
rf.matchIndex[server] = newMathIdx
}
rf.nextIndex[server] = rf.matchIndex[server] + 1
// 更新提交的日志
rf.updateCommitIndex()
} else {
// 如果失败,根据 reply.ConflictIndex 实现快速跳转 ------ 对应论文:If AppendEntries fails because of log inconsistency, decrement nextIndex and retry
if reply.ConflictTerm == -1 {
// 日志过短
rf.nextIndex[server] = reply.ConflictIndex
} else {
flag := false
for i := args.PrevLogIndex; i >= rf.LastIncludedIndex; i-- {
if rf.getTermByIndex(i) == reply.ConflictTerm {
flag = true
rf.nextIndex[server] = i + 1
break
}
}
if !flag {
rf.nextIndex[server] = reply.ConflictIndex
}
}
}
}
return ok
}
2.6 updateCommitIndex:Leader 如何合法推进提交
这节写清楚 Figure 8 约束(非常重要):
- Leader 推进到某个 index
N的条件:
-
多数派
matchIndex[i] >= N -
且
logs[N].Term == rf.term(只用当前任期的日志推进提交,间接提交旧任期日志)
推荐这样写:
-
从后往前找最大的 N(更快)
-
注意边界:
len(logs)-1和len(logs)
Go
// 日志提交应用-leader专属(matchIndex/commitIndex 均为逻辑索引)
func (rf *Raft) updateCommitIndex() {
if rf.state != Leader {
return
}
for i := rf.getLastIndex(); i > rf.commitIndex; i-- {
if rf.getTermByIndex(i) == rf.currentTerm {
cnt := 1
for j := range rf.peers {
if j != rf.me && rf.matchIndex[j] >= i {
cnt++
}
}
if cnt >= len(rf.peers)/2+1 {
rf.commitIndex = i
rf.applyCond.Broadcast()
break
}
} else if rf.getTermByIndex(i) < rf.currentTerm {
break
}
}
}
2.7 applier:把已提交日志送到 applyChan(不要持锁发 chan)
在这部分中需要特别注意的是:
-
不要用 sleep 轮询:延迟大、测试反馈慢
-
用 sync.Cond :commitIndex 增加时
Broadcast唤醒 applier -
锁内拷贝、锁外发送 :避免
applyChan阻塞时卡死整个 Raft
来看这个简短伪代码流程:
-
lock → 等待
commitIndex > lastApplied(cond.Wait) -
计算要 apply 的区间,拷贝 entries
-
unlock → 逐条向
applyChan发送 -
lock → 更新
lastApplied→ unlock
2.8 3B 典型 Bug 记录
我按表格整理了一下常见的坑,我基本都是踩过的:
| 问题分类 | 具体描述 |
|---|---|
| Agreement Failed(commitIndex 没推进 / applier 没被唤醒) | 就是集群明明已经达成一致了,但 commitIndex 死活不涨,或者涨了却没人去 apply。要么是 Leader 忘了给 Follower 发心跳,要么是 Follower 收到了心跳却没更新自己的 commitIndex,再要么是 applyLoop 没被唤醒,卡在 channel 上没人通知。 |
| too many RPCs(心跳过频,缺少节流) | 每个日志条目都单独发一次 RPC,或者心跳发得太快,把网络打满。正确做法是用 lastBroadcastTime 做个节流,比如 10ms 内只发一次心跳,没必要每条日志都立刻广播。 |
| 回退太慢(未实现 conflict 优化) | 日志冲突之后,如果只靠 nextIndex-- 一格格往回退,遇到恶意日志会退几百次,导致同步极慢。用论文里的 conflict term 和 conflict index 做快速回退,一次性跳到冲突点,性能好很多。 |
| 死锁(持锁发 chan / 持锁做重活) | 最常见的是 持有锁然后向 applyCh 发消息 ,如果对端处理慢或者也来拿同一把锁,就死锁了。还有就是 持有锁做耗时的磁盘写入 ,虽然不死锁,但会卡住整个 Raft 状态机。解决方法是 先拷贝需要的数据,解锁,再发 chan 或写盘。 |
这些 Bug 在 3B 阶段几乎每个人都会遇到,提前有个印象能省不少调试时间。
三、Part C:持久化(Persistence,3C)
3.1 3C 的核心:持久化"承诺",不是持久化"结果"
一句话:只要重启可能导致你做出与重启前矛盾的行为,就必须在返回 RPC 之前把状态写盘,其实很简单,只要将需要持久化的内容每次改动的时候进行持久化就可以了。
3.2 persist/readPersist:持久化内容与编码格式
持久化内容(Figure 2):
-
rf.term -
rf.votedFor -
rf.logs
编码顺序必须和解码顺序一致,否则 readPersist (会失忆/乱序),这里也是一个坑,但是我这里为了方便怎么存的怎么取,所以把坑跳过去了。
Go
func (rf *Raft) persist() {
// Your code here (3C).
// Example:
// w := new(bytes.Buffer)
// e := labgob.NewEncoder(w)
// e.Encode(rf.xxx)
// e.Encode(rf.yyy)
// raftstate := w.Bytes()
// rf.persister.Save(raftstate, nil)
w := new(bytes.Buffer)
e := labgob.NewEncoder(w)
e.Encode(rf.currentTerm)
e.Encode(rf.votedFor)
e.Encode(rf.logs)
e.Encode(rf.LastIncludedIndex)
e.Encode(rf.LastIncludedTerm)
raftstate := w.Bytes()
snap := rf.persister.ReadSnapshot()//先读取快照
rf.persister.Save(raftstate, snap)//再持久化状态和快照
}
// restore previously persisted state.
func (rf *Raft) readPersist(data []byte) {
if data == nil || len(data) < 1 { // bootstrap without any state?
return
}
// Your code here (3C).
// Example:
r := bytes.NewBuffer(data)
d := labgob.NewDecoder(r)
var term int
var vorfor int
var logs []LogEntry
var lastincludedindex int
var lastincludedterm int
if d.Decode(&term) != nil ||
d.Decode(&vorfor) != nil ||
d.Decode(&logs) != nil ||
d.Decode(&lastincludedindex) != nil ||
d.Decode(&lastincludedterm) != nil {
DPrintf("readPersist err")
} else {
rf.currentTerm = term
rf.votedFor = vorfor
rf.logs = logs
rf.LastIncludedIndex = lastincludedindex
rf.LastIncludedTerm = lastincludedterm
}
}
3.3 何时调用 persist:时机与原子性
可以按谁改了谁负责 persist写成清单:
-
startElection :
term++、votedFor=me之后 -
RequestVote :更新
term/votedFor之后(尤其是投票成功路径) -
AppendEntries:看到更大 term 退位时;日志截断/追加时
-
Start:追加日志后
关键点:一定要在 handler 返回 reply 之前 persist(否则崩溃重启后可能同任期重复投票,造成脑裂等一系列问题)。
3.4 为什么 3B 正常、3C 就崩
-
3B 都过了,3C 断电测试出现 Log Inconsistency
-
根因通常是:readPersist 没写 / 解码顺序错 / logs 没恢复占位条目
-
commitIndex/lastApplied/nextIndex/matchIndex不需要持久化,它们会在重启后通过 Leader 的 LeaderCommit、以及日志复制流程动态恢复。
3.5 性能与正确性的取舍
-
persist 频率高会拖慢系统(尤其心跳很频繁时)
-
心跳频率要"优雅":足够快维持领导权,但不能淹没网络(too many RPCs)
-
正确性优先:该 persist 的地方不能省;优化要在正确性上做节流/批量/减少无效 RPC
四、测试和遇到的问题
bash
cd 6.5840/src
make RUN="-run 3B" raft1
make RUN="-run 3C" raft1
3B 阶段主要折腾的是 Raft 里两个搬运者:谁在什么时候推进提交、谁把已提交日志交给状态机。我对这两块做了比较彻底的重构。
updateCommitIndex(Leader 侧)
问题:一开始有数组越界(
len和len-1混用)、以及用「日志下标」去当matchIndex的下标这类逻辑错误。做法:改成从后往前扫合法的提交点 NN,并严格按 Figure 8:Leader 只能用「当前任期里复制到多数派的那条日志」去推进
commitIndex,旧任期的条目只能被顺带提交。Figure 2 流水线(自测时心里要有这条链)
Start写日志 → Leader 发AppendEntries→ Follower 确认并追加 → Leader 用matchIndex推进commitIndex并唤醒 applier → 后续心跳带上LeaderCommit,Follower 跟进commitIndex并同样唤醒 applier → 双方经applyChan按序应用。
*实现中需要注意的点:
- 尽量不要在持锁时写 channel。
- 等 commit 用
sync.Cond,少用忙等 / 固定Sleep。- 心跳:够快以维持领导权,但不要打到 RPC 风暴。
readPersist- 这是 3C 里很隐蔽的一类问题:没实现、或 Decode 顺序和 Encode 对不上,节点等于「失忆」,往往在断电测试里表现为大量 Log Inconsistency,而纯 3B 有时还能蒙混过关。
- 性能和正确性
HeartBeatTimeout、RPC 频率和persist次数会互相拉扯;变了就存最安全,但心跳极密时无脑persist会把测试拖慢。可以在保证该存必存的前提下,适当拉长心跳间隔、减少无意义刷盘(具体以你当时通过测试的配置为准)。- 哪些不用存
commitIndex等易失状态不必持久化;重启后靠 Leader 的 LeaderCommit 和日志一致性检查再对齐即可。其它 3B 测试里碰到的
问题 现象 原因与处理 Agreement 失败 one(100)等闭环缺一环:Leader 收到成功回复没更新提交;或 Follower 更新了 commitIndex却没唤醒 applier。RPC 过多 too many RPCs心跳发得太勤(例如 ticker 里固定短间隔全员 RPC)。需要按间隔节流(如配合选举/心跳超时)。 冲突回退慢 性能测试超时 nextIndex一格一格退。应实现 ConflictTerm / ConflictIndex,让 Leader 整块对齐。死锁 测试 hang 持锁路径里 RPC 回调或 apply 过重。缩小锁粒度,禁止持锁写 channel。 3C:持久化(测试拉长后才会暴露)
何时
persist、保的是什么
我一开始在Start里就落了盘,其它 RPC 路径也零零散散加了一些persist,跑 3C 才发现还有路径没覆盖全。
要点:持久化保的不是业务结果,而是 承诺------凡是崩溃重启后可能做出和崩溃前矛盾的事(例如同一任期投两次票),就要在 对外承诺已经形成之后、RPC 返回之前 把该存的状态写盘。applier(应用侧)
- 问题:早期用
Sleep轮询commitIndex,测试反馈慢;还有在持锁时往applyChan里送消息,容易把整个节点卡死。- 做法:用
sync.Cond,在commitIndex前进时Broadcast;发送 apply 消息时坚持 锁里只拷贝、锁外再写 channel,避免 channel 阻塞拖死持锁路径。
这是最终测试通过的输出
3B
lcz@iv-yef3xahqtc5i3z5jzmr5:~/mit6.5840/6.5840/src$ make RUN="-run 3B" raft1
go build -race -o main/raft1d main/raft1d.go cd raft1 && go test -v -race -run 3B
=== RUN TestBasicAgree3B Test (3B): basic agreement (reliable network)
... ... Passed -- time 0.4s #peers 3 #RPCs 14 #Ops 3
--- PASS: TestBasicAgree3B (0.71s)
=== RUN TestRPCBytes3B Test (3B): RPC byte count (reliable network)
... ... Passed -- time 1.8s #peers 3 #RPCs 58 #Ops 11
--- PASS: TestRPCBytes3B (2.14s)
=== RUN TestFollowerFailure3B Test (3B): test progressive failure of followers (reliable network)...
... Passed -- time 4.3s #peers 3 #RPCs 188 #Ops 3
--- PASS: TestFollowerFailure3B (4.67s)
=== RUN TestLeaderFailure3B Test (3B): test failure of leaders (reliable network)...
... Passed -- time 4.7s #peers 3 #RPCs 294 #Ops 3
--- PASS: TestLeaderFailure3B (5.03s)
=== RUN TestFailAgree3B Test (3B): agreement after follower reconnects (reliable network)...
... Passed -- time 3.9s #peers 3 #RPCs 134 #Ops 7
--- PASS: TestFailAgree3B (4.37s)
=== RUN TestFailNoAgree3B Test (3B): no agreement if too many followers disconnect (reliable network)...
... Passed -- time 3.3s #peers 5 #RPCs 316 #Ops 2
--- PASS: TestFailNoAgree3B (3.81s)
=== RUN TestConcurrentStarts3B Test (3B): concurrent Start()s (reliable network)... ... Passed -- time 0.6s #peers 3 #RPCs 24 #Ops 0
--- PASS: TestConcurrentStarts3B (1.07s)
=== RUN TestRejoin3B Test (3B): rejoin of partitioned leader (reliable network)...
... Passed -- time 5.7s #peers 3 #RPCs 282 #Ops 4
--- PASS: TestRejoin3B (6.05s)
=== RUN TestBackup3B Test (3B): leader backs up quickly over incorrect follower logs (reliable network)...
... Passed -- time 19.1s #peers 5 #RPCs 2568 #Ops 102
--- PASS: TestBackup3B (19.68s)
=== RUN TestCount3B Test (3B): RPC counts aren't too high (reliable network)...
... Passed -- time 2.2s #peers 3 #RPCs 72 #Ops 0
--- PASS: TestCount3B (2.71s) PASS ok 6.5840/raft1 51.273s
3C
lcz@iv-yef3xahqtc5i3z5jzmr5:~/mit6.5840/6.5840/src$ make RUN="-run 3C" raft1
go build -race -o main/raft1d main/raft1d.go
cd raft1 && go test -v -race -run 3C
=== RUN TestPersist13C
Test (3C): basic persistence (reliable network)...
... Passed -- time 3.3s #peers 3 #RPCs 98 #Ops 6
--- PASS: TestPersist13C (3.75s)
=== RUN TestPersist23C
Test (3C): more persistence (reliable network)...
... Passed -- time 13.7s #peers 5 #RPCs 568 #Ops 16
--- PASS: TestPersist23C (14.36s)
=== RUN TestPersist33C
Test (3C): partitioned leader and one follower crash, leader restarts (reliable network)...
... Passed -- time 1.5s #peers 3 #RPCs 48 #Ops 4
--- PASS: TestPersist33C (1.84s)
=== RUN TestFigure83C
Test (3C): Figure 8 (reliable network)...
2026/03/18 22:15:05 6PCdkEFTs2eiMT_RlFB1: dmxsrv.reader: clnt ACu3swj2_8gbs1Wd6nbn ReadCall err read unix /tmp/6.5840-6PCdkEFTs2eiMT_RlFB1->@: read: connection reset by peer
2026/03/18 22:15:47 6PCdkEFTs2eiMT_RlFB1: dmxsrv.reader: clnt 5VPxjteaE_i4P1o9h71T ReadCall err read unix /tmp/6.5840-6PCdkEFTs2eiMT_RlFB1->@: read: connection reset by peer
... Passed -- time 51.8s #peers 5 #RPCs 2369 #Ops 2
--- PASS: TestFigure83C (52.30s)
=== RUN TestUnreliableAgree3C
Test (3C): unreliable agreement (unreliable network)...
... Passed -- time 3.4s #peers 5 #RPCs 220 #Ops 246
--- PASS: TestUnreliableAgree3C (4.02s)
=== RUN TestFigure8Unreliable3C
Test (3C): Figure 8 (unreliable) (unreliable network)...
... Passed -- time 48.0s #peers 5 #RPCs 7496 #Ops 2
2026/03/18 22:16:44 T5AgHTtizWPRzjsYwAQ6: dmxsrv.reader: clnt UkD_e2Q-b5OG3f1PBPSa ReadCall err read unix /tmp/6.5840-T5AgHTtizWPRzjsYwAQ6->@: read: connection reset by peer
--- PASS: TestFigure8Unreliable3C (48.77s)
=== RUN TestReliableChurn3C
Test (3C): churn (reliable network)...
... Passed -- time 16.6s #peers 5 #RPCs 1084 #Ops 1
--- PASS: TestReliableChurn3C (17.17s)
=== RUN TestUnreliableChurn3C
Test (3C): unreliable churn (unreliable network)...
... Passed -- time 16.8s #peers 5 #RPCs 1028 #Ops 1
--- PASS: TestUnreliableChurn3C (17.46s)
PASS
ok 6.5840/raft1 160.685s
五、收获
在看完论文理解之后lab3还是较简单的,但是对于test中其实并不能test出你代码的漏洞,就像我之前ticker设置的检索间隔一样。照样是通过了所有test。但是在B中却因为RPC调用太频繁超时了。我们在代码实现中要使用尽量多的调试信息打印,以便后续调试,希望我的思路可以带给你们思路,然后按照自己的思路实现出属于自己的lab1。最后希望有错误的地方多指正指正~
- 完整实现:mit6.5840: 用来记录mit6.5840(原6.824)的实现历程
- 觉得有收获的可以帮忙点个star~