MIT 6.824 lab3B/C


前言

花两天把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.termrf.votedForrf.logs

  • 易失状态(所有节点)rf.commitIndexrf.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/论文一致,最好对着一步步走:

  1. term 检查args.Term < rf.term 直接拒绝,回 reply.Term = rf.term

  2. 退位与重置计时器 :收到合法 Leader 的 RPC → lastHeartBeatTime = now,必要时退回 Follower

  3. 一致性检查 :用 PrevLogIndex/PrevLogTerm 验证日志前缀一致

  • 本地太短(PrevLogIndex 超界)→ Success=false

  • term 不匹配 → Success=false

  1. 截断并追加 :从 PrevLogIndex+1 开始,删除本地冲突后缀,再追加 Entries

  2. 推进 commitIndexcommitIndex = 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)-1len(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写成清单:

  • startElectionterm++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 侧)

  • 问题:一开始有数组越界(lenlen-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。最后希望有错误的地方多指正指正~

相关推荐
java1234_小锋2 小时前
RabbitMQ中有哪几种交换机类型?
分布式·rabbitmq
凤山老林2 小时前
Spring Boot 集成 TigerGraph 实现图谱分析技术方案
java·spring boot·后端·图谱分析·tigergraph
代码漫谈2 小时前
探索RabbitMQ集群:如何实现消息的高可用性和负载均衡
分布式·消息队列·rabbitmq·负载均衡
Victor3562 小时前
MongoDB(106)什么是MongoDB Compass?
后端
.生产的驴2 小时前
SpringBoot 大文件分片上传 文件切片、断点续传与性能优化 切片技术与优化方案 文件高效上传
java·服务器·spring boot·后端·spring·spring cloud·状态模式
Victor3562 小时前
MongoDB(105)如何解决MongoDB中的内存泄漏问题?
后端
吴文周10 小时前
告别重复劳动:一套插件让 AI 替你写代码、修Bug、做测试、上生产
前端·后端·ai编程
Cyeam10 小时前
Roadbook CSV:一行 CSV 秒变高德地图路书
后端·开源·aigc
懒狗小前端11 小时前
做了一个 codex 的中文文档网站,做的不好可以随便喷
前端·后端