lab2 第一次完结,在ai帮助实现了大概,周三周四打算自己手写一遍,提升工程能力
感触:
1 up本来是java后端选手,有过一点cpp基础,被mt说需要提高工程能力,故开始学习mit6.824 提高工程能力,lab1就是ai帮忙下,自己手写一半完成实验,后面自己全删掉又重写一遍,感觉没有go基础也能在ai帮助下快速提升。
2 论文都看了,但是自己上手写的时候还是有点手生,还是要多谢代码。lab2a主要是选主,不是很难,但是尤其,因为没有日志需要记录,所以这个时候选主只需要判断任期和对应是否投票就好了,其次是选举超时时间和心跳时间需要设计好比例关系,论文这里面有详细说明。
3 lab2b,主要是对日志的记录,这个时候尤其需要主要锁的使用,避免死锁和对应的竞态,主要是rpc调用,和对应状态修改需要注意。
4 lab2c比较容易,只要又更改任期号,投票和日志,在锁内持续化即可。
后言:全更新,改了好久,发现临界变量很多地方都需要改
选举重点实现关注:
发起选举:
1. 更改自己的状态,从Follower到Candidate,增加任期,并投票给自己
2. 重置心跳,并为选举超时时间随机选取时间
3. 持久化,votefor currentTerm logs 持续化
4. 并发发送请求拉票,注意发送请求是参数别传指针,会有竟态风险。
处理投票 (RequestVote RPC):
-
任期检查:
args.Term < rf.currentTerm:拒绝(陈旧的选举)。args.Term > rf.currentTerm:降级为 Follower,更新rf.currentTerm,重置rf.votedFor = -1,并持久化。
-
投票逻辑: 只有在
rf.votedFor == -1(未投票) 或rf.votedFor == args.CandidateID(重复请求) 时才可能投票。 -
判断日志最新检查 : 这是 Raft 的核心安全机制。:
if rf.log[lastIndex].Term > args.LastLogTerm || (rf.log[lastIndex].Term == args.LastLogTerm && lastIndex > args.LastLogIndex),拒绝投票给日志不如自己新的 Candidate。
选举成功/失败:
- 成功: 在
startElection的回调中,如果votes > (len(rf.peers) / 2)并且rf.role == Candidate(防止旧 RPC 影响新状态),节点成为Leader。 - 成为 Leader: 立即初始化
nextIndex和matchIndex数组,并启动go rf.runLeaderLoop()开始持续发送心跳。 - 失败 (Split Vote): 选举超时,
runElectionTimer会再次触发,startElection会递增currentTerm并开始新一轮选举。 - 失败 (发现新 Leader):
AppendEntriesRPC 会使 Candidate 降级为 Follower。
日志 (Log Replication):
数据结构:
rf.log:[]LogEntry数组,你正确地使用rf.log[0](Term=0) 作为哨兵条目,使日志索引从 1 开始,大大简化了边界条件处理。nextIndex[]: Leader 维护,表示 下一个 要发送给 Follower 的日志索引。matchIndex[]: Leader 维护,表示已知已在 Follower 上 成功复制 的最高索引。commitIndex: 所有节点 维护,已知的、被多数节点复制的最高日志索引。lastApplied: 所有节点 维护,已应用到状态机的最高日志索引。
Leader 接受日志 (Start):
-
Leader 收到
Start(command),将{Term: rf.currentTerm, Command: command}追加到 自己 的rf.log中,并调用rf.persist()持久化。此时日志 尚未提交。 -
-
Leader 接受日志 (
Start): -
Leader 收到
Start(command),将{Term: rf.currentTerm, Command: command}追加到 自己 的rf.log中,并调用rf.persist()持久化。此时日志 尚未提交。
-
-
Leader 复制日志 (
runLeaderLoop和replicateLogToPeer):-
runLeaderLoop周期性(heartbeatInterval)地为每个 peer 调用go rf.replicateLogToPeer。 -
replicateLogToPeer是日志复制的核心:-
准备
AppendEntriesArgs: Leader 根据rf.nextIndex[server]确定PrevLogIndex和PrevLogTerm,并附上rf.log[nextIdx:]的所有条目(如果entriesToSend为空,这就是一个心跳)。 -
发送 RPC。
-
处理回复 (关键):
-
reply.Success == true(成功): 更新rf.matchIndex[server]和rf.nextIndex[server]。然后调用rf.updateCommitIndexUnlocked()尝试推进commitIndex。 -
reply.Success == false(失败): 这是 Follower 日志不一致。你的实现包含了 "快速回退" 优化:-
Follower 在
AppendEntries中返回了ConflictTerm和ConflictIndex。 -
Leader 使用这些信息:
- 如果
ConflictTerm存在于 Leader 的日志中,Leader 会找到自己日志中该任期的 最后一条 记录,并设置newNextIndex = i + 1。 - 如果
ConflictTerm不存在,Leader 直接设置newNextIndex = reply.ConflictIndex。
- 如果
-
这比每次只回退 1 个索引 (
nextIndex--) 要高效得多。
-
-
-
-
-
Follower 接受日志 (
AppendEntriesRPC):-
一致性检查:
args.PrevLogIndex > len(rf.log)-1(日志太短):拒绝,返回ConflictIndex = len(rf.log)。rf.log[args.PrevLogIndex].Term != args.PrevLogTerm(任期冲突):拒绝,返回ConflictTerm = rf.log[args.PrevLogIndex].Term和该任期的 第一个 索引ConflictIndex。
-
接受日志:
- 处理冲突: 你的代码使用
sameIndex查找第一个不匹配的条目,然后使用rf.log = append(rf.log[:sameIndex+args.PrevLogIndex], args.Entries[sameIndex-1:]...)来 截断 旧的冲突日志并追加新日志。这是处理 Follower 日志与 Leader 不一致的标准方法。 - 持久化:
rf.persist()。 - 更新 Commit:
rf.commitIndex = min(len(rf.log)-1, args.LeaderCommit)。 - 唤醒 Applier:
rf.applyCond1.Signal()。
- 处理冲突: 你的代码使用
-
-
提交与应用 (
updateCommitIndexUnlocked和applierLoop):-
Leader 提交: 在
updateCommitIndexUnlocked中,Leader 查找一个N > rf.commitIndex,如果rf.log[N].Term == rf.currentTerm且matchIndex中有 过半数 成员matchIndex[i] >= N,Leader 就将rf.commitIndex更新为N。 -
应用到状态机:
applierLoop(goroutine) 负责应用。- 它使用
sync.Cond(rf.applyCond1.Wait()) 高效地等待,直到rf.commitIndex > rf.lastApplied。 - (关键)防止死锁: 你的实现非常棒。它在持有锁时,将
rf.log[startIdx:endIdx+1]复制到局部变量logsToApply中,然后释放锁 (rf.mu.Unlock())。 - 最后,它在 锁外 向
rf.applyCh发送消息。这避免了applyCh阻塞(如果 service/tester 消费慢)时导致整个 Raft 节点死锁。
- 它使用
-
```
package raft
//
// this is an outline of the API that raft must expose to
// the service (or tester). see comments below for
// each of these functions for more details.
//
// rf = Make(...)
// create a new Raft server.
// rf.Start(command interface{}) (index, term, isleader)
// start agreement on a new log entry
// rf.GetState() (term, isLeader)
// ask a Raft for its current term, and whether it thinks it is leader
// ApplyMsg
// each time a new entry is committed to the log, each Raft peer
// should send an ApplyMsg to the service (or tester)
// in the same server.
//
import (
"bytes"
"log"
"math/rand"
"sync"
"time"
)
import "sync/atomic"
import "../labrpc"
import "../labgob"
func min(a, b int) int {
if a < b {
return a
}
return b
}
// as each Raft peer becomes aware that successive log entries are
// committed, the peer should send an ApplyMsg to the service (or
// tester) on the same server, via the applyCh passed to Make(). set
// CommandValid to true to indicate that the ApplyMsg contains a newly
// committed log entry.
//
// in Lab 3 you'll want to send other kinds of messages (e.g.,
// snapshots) on the applyCh; at that point you can add fields to
// ApplyMsg, but set CommandValid to false for these other uses.
type ApplyMsg struct {
CommandValid bool
Command interface{}
CommandIndex int
}
// LogEntry 包含命令和 Leader 收到它时的任期号
type LogEntry struct {
Term int
Command interface{}
}
// Raft 节点的状态
type state int
const (
Follower state = iota
Candidate
Leader
)
// A Go object implementing a single Raft peer.
type Raft struct {
mu sync.Mutex // Lock to protect shared access to this peer's state
peers []*labrpc.ClientEnd // RPC end points of all peers
persister *Persister // Object to hold this peer's persisted state
me int // this peer's index into peers[]
dead int32 // set by Kill()
// Your data here (2A, 2B, 2C).
// Look at the paper's Figure 2 for a description of what
// state a Raft server must maintain.
// 持久化状态 (所有服务器)
currentTerm int
votedFor int
log []LogEntry
// 易失状态 (所有服务器)
role state
commitIndex int
lastApplied int
// 易失状态 (Leader)
nextIndex []int
matchIndex []int
// 定时器相关
electionTimeout time.Duration
lastHeartbeat time.Time
heartbeatInterval time.Duration
// 通道
applyCh chan ApplyMsg
//条件变量
applyCond1 *sync.Cond
}
// return currentTerm and whether this server
// believes it is the leader.
func (rf *Raft) GetState() (int, bool) {
rf.mu.Lock()
defer rf.mu.Unlock()
// Your code here (2A).
var term int
var isleader bool
term = rf.currentTerm
isleader = (rf.role == Leader)
return term, isleader
}
// save Raft's persistent state to stable storage,
// where it can later be retrieved after a crash and restart.
// see paper's Figure 2 for a description of what should be persistent.
func (rf *Raft) persist() {
// Your code here (2C).
// Example:
w := new(bytes.Buffer)
e := labgob.NewEncoder(w)
e.Encode(rf.currentTerm)
e.Encode(rf.votedFor)
e.Encode(rf.log)
data := w.Bytes()
rf.persister.SaveRaftState(data)
}
// restore previously persisted state.
func (rf *Raft) readPersist(data []byte) {
if data == nil || len(data) < 1 { // bootstrap without any state?
return
}
// Your code here (2C).
// Example:
r := bytes.NewBuffer(data)
d := labgob.NewDecoder(r)
var currentTerm int
var votedFor int
var logs []LogEntry
if d.Decode(¤tTerm) != nil ||
d.Decode(&votedFor) != nil ||
d.Decode(&logs) != nil {
log.Fatalf("persist 失败")
} else {
rf.currentTerm = currentTerm
rf.votedFor = votedFor
rf.log = logs
}
}
// RequestVote RPC arguments structure.
type RequestVoteArgs struct {
// Your data here (2A, 2B).
Term int
CandidateID int
LastLogIndex int
LastLogTerm int
}
// RequestVote RPC reply structure.
type RequestVoteReply struct {
// Your data here (2A).
Term int
VoteGranted bool
}
// RequestVote RPC handler.
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
// Your code here (2A, 2B, 2C).
rf.mu.Lock()
defer rf.mu.Unlock()
if atomic.LoadInt32(&rf.dead) == 1 {
reply.VoteGranted = false
reply.Term = rf.currentTerm
return
}
if args.Term > rf.currentTerm {
rf.currentTerm = args.Term
rf.votedFor = -1 // 重置投票
rf.role = Follower // 降级为 Follower
rf.persist() // 持久化新的任期和 votedFor
// 注意:不要在这里 return,继续下面的投票逻辑
}
if args.Term < rf.currentTerm {
reply.VoteGranted = false
reply.Term = rf.currentTerm
return
}
if rf.votedFor == -1 || rf.votedFor == args.CandidateID {
if rf.votedFor == args.CandidateID {
reply.VoteGranted = true
reply.Term = rf.currentTerm
rf.lastHeartbeat = time.Now()
return
}
lastIndex := len(rf.log) - 1
if rf.log[lastIndex].Term > args.LastLogTerm || (rf.log[lastIndex].Term == args.LastLogTerm && lastIndex > args.LastLogIndex) {
reply.VoteGranted = false
reply.Term = rf.currentTerm
return
}
reply.VoteGranted = true
reply.Term = rf.currentTerm
rf.votedFor = args.CandidateID
rf.persist()
rf.lastHeartbeat = time.Now()
return
}
reply.VoteGranted = false
reply.Term = rf.currentTerm
return
}
// AppendEntries RPC arguments structure.
type AppendEntriesArgs struct {
// Your data here (2A, 2B).
Term int
LeaderId int
Entries []LogEntry
PrevLogIndex int
PrevLogTerm int
LeaderCommit int
}
// AppendEntries RPC reply structure.
type AppendEntriesReply struct {
// Your data here (2A, 2B).
Term int
Success bool
ConflictTerm int // 冲突条目的任期号 (如果存在)
ConflictIndex int // 冲突任期的第一个索引
}
// AppendEntries RPC handler.
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
// Your code here (2A, 2B, 2C).
rf.mu.Lock()
defer rf.mu.Unlock()
if args.Term > rf.currentTerm {
rf.currentTerm = args.Term
rf.votedFor = -1
rf.role = Follower
rf.persist()
}
if args.Term < rf.currentTerm {
reply.Success = false
reply.Term = rf.currentTerm
return
}
rf.lastHeartbeat = time.Now()
if args.PrevLogIndex > len(rf.log)-1 {
reply.Success = false
reply.Term = rf.currentTerm
// 告诉 Leader,Follower 的日志太短了
reply.ConflictIndex = len(rf.log) // Leader 应该从这里开始重试
reply.ConflictTerm = 0 // 0 (或 -1) 表示没有冲突的任期
return
}
if rf.log[args.PrevLogIndex].Term != args.PrevLogTerm {
reply.Success = false
reply.Term = rf.currentTerm
// 告诉 Leader 冲突的任期号
reply.ConflictTerm = rf.log[args.PrevLogIndex].Term
// 找到这个冲突任期的 *第一个* 索引
conflictIdx := args.PrevLogIndex
// 向前搜索,直到找到一个任期不同的条目
for conflictIdx > 0 && rf.log[conflictIdx-1].Term == reply.ConflictTerm {
conflictIdx--
}
reply.ConflictIndex = conflictIdx // Leader 应该从这个索引开始重试
return
}
sameIndex := 1
for i := 0; i < len(args.Entries); i++ {
if sameIndex+args.PrevLogIndex > len(rf.log)-1 {
break
}
if args.Entries[i].Term != rf.log[args.PrevLogIndex+sameIndex].Term {
break
}
sameIndex++
}
rf.log = append(rf.log[:sameIndex+args.PrevLogIndex], args.Entries[sameIndex-1:]...)
rf.persist()
reply.Success = true
reply.Term = rf.currentTerm
rf.commitIndex = min(len(rf.log)-1, args.LeaderCommit)
if rf.commitIndex > rf.lastApplied {
rf.applyCond1.Signal()
}
}
// example code to send a RequestVote RPC to a server.
func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {
// Your code here.
ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
return ok
}
// the service using Raft (e.g. a k/v server) wants to start
// agreement on the next command to be appended to Raft's log.
func (rf *Raft) Start(command interface{}) (int, int, bool) {
// Your code here (2B, 2C).
rf.mu.Lock()
defer rf.mu.Unlock()
index := -1
term := -1
isLeader := false
if rf.role != Leader {
return index, term, isLeader
}
rf.log = append(rf.log, LogEntry{Term: rf.currentTerm, Command: command})
rf.persist()
index = len(rf.log) - 1
term = rf.currentTerm
isLeader = true
return index, term, isLeader
}
// the tester doesn't halt goroutines created by Raft after each test,
// but it does call the Kill() method.
func (rf *Raft) Kill() {
atomic.StoreInt32(&rf.dead, 1)
// Your code here, if desired.
}
func (rf *Raft) killed() bool {
z := atomic.LoadInt32(&rf.dead)
return z == 1
}
// 核心循环和后台 Goroutines
//
// Your code here (2A).
// for !rf.killed() {
// 1. 睡眠一段时间 (time.Sleep)
// 2. 检查是否超时 (rf.mu.Lock(), 检查 rf.role 和 time.Now().After(...), rf.mu.Unlock())
// 3. 如果超时,发起选举 (rf.startElection)
// }
//
// runElectionTimer 是选举定时器的主循环
func (rf *Raft) runElectionTimer() {
const pollInterval = 10 * time.Millisecond
for !rf.killed() {
time.Sleep(pollInterval)
rf.mu.Lock()
if time.Now().Sub(rf.lastHeartbeat) > rf.electionTimeout {
if rf.role == Follower || rf.role == Candidate {
go rf.startElection()
}
}
rf.mu.Unlock()
}
}
// Your code here (2A, 2B, 2C).
// 1. 加锁
// 2. 转换角色为 Candidate, 增加 currentTerm, 投票给自己, 重置选举超时
// 3. 调用 rf.persist()
// 4. 准备 RequestVoteArgs
// 5. 解锁
// 6. 并发地向所有 peers 发送 sendRequestVote (go func(peer int) { ... })
// 7. 在每个 goroutine 中:
// a. 处理 RPC 回复
// b. 加锁
// c. 检查状态 (确保任期和角色没有改变)
// d. 如果回复的任期更高,降级为 Follower (并 persist)
// e. 如果获得选票:
// i. 增加票数 (atomic.AddInt32)
// ii. 检查是否过半
// iii. (关键) 再次检查 rf.role == Candidate (防止竞态)
// iv. 成为 Leader: 设置 rf.role, 初始化 nextIndex/matchIndex, 启动 go rf.runLeaderLoop()
// f. 解锁
//
// startElection 发起一次选举
func (rf *Raft) startElection() {
rf.mu.Lock()
rf.role = Candidate
rf.electionTimeout = time.Duration((300 + rand.Intn(200))) * time.Millisecond
rf.persist()
rf.lastHeartbeat = time.Now()
rf.currentTerm++
rf.votedFor = rf.me
//快照
currentTerm_snapshot := rf.currentTerm
lastLogIndex_snapshot := len(rf.log) - 1
lastLogTerm_snapshot := 0
if lastLogIndex_snapshot > 0 { // 假设 rf.log[0] 是哨兵
lastLogTerm_snapshot = rf.log[lastLogIndex_snapshot].Term
}
rf.mu.Unlock()
var votes int32 = 1
for i := 0; i < len(rf.peers); i++ {
if i == rf.me {
continue
}
args := RequestVoteArgs{
Term: currentTerm_snapshot,
CandidateID: rf.me,
LastLogIndex: lastLogIndex_snapshot,
LastLogTerm: lastLogTerm_snapshot,
}
reply := RequestVoteReply{}
// 修复 goroutine 陷阱
argsCopy := args
replyCopy := reply
go func(server int, args RequestVoteArgs, reply RequestVoteReply) {
ok := rf.sendRequestVote(server, &args, &reply)
if !ok {
return
}
rf.mu.Lock()
defer rf.mu.Unlock()
if reply.Term > rf.currentTerm {
rf.currentTerm = reply.Term
rf.role = Follower
rf.persist()
return
}
if rf.currentTerm != args.Term || rf.role != Candidate {
return
}
if reply.VoteGranted {
atomic.AddInt32(&votes, 1)
if votes > (int32(len(rf.peers) / 2)) {
if rf.role == Candidate {
rf.role = Leader
for i, _ := range rf.peers {
rf.nextIndex[i] = len(rf.log)
rf.matchIndex[i] = 0
}
rf.persist()
go rf.runLeaderLoop()
}
}
}
}(i, argsCopy, replyCopy)
}
}
// Your code here (2A, 2B).
// for !rf.killed() {
// 1. 加锁, 检查自己是否还是 Leader, 解锁
// 2. 向所有 peers 并发地发送心跳/日志 (go rf.replicateLogToPeer(peer))
// 3. 睡眠心跳间隔 (time.Sleep(rf.heartbeatInterval))
// }
//
// runLeaderLoop 是 Leader 的主循环,负责定时发送心跳
func (rf *Raft) runLeaderLoop() {
for !rf.killed() {
rf.mu.Lock()
if rf.role != Leader {
rf.mu.Unlock()
return
}
rf.mu.Unlock()
for i := 0; i < len(rf.peers); i++ {
if i == rf.me {
continue
}
go rf.replicateLogToPeer(i)
}
time.Sleep(rf.heartbeatInterval)
}
}
// Your code here (2B, 2C).
// 1. 加锁
// 2. 检查是否还是 Leader
// 3. 准备 AppendEntriesArgs (根据 rf.nextIndex[server] 确定 prevLogIndex 和 entries)
// 4. 解锁
// 5. 发送 RPC (rf.peers[server].Call(...))
// 6. 处理回复 (AppendEntriesReply)
// 7. 加锁
// 8. (关键) 检查状态:rf.role == Leader && rf.currentTerm == args.Term (防止陈旧回复)
// 9. 如果回复的任期更高,降级为 Follower (并 persist)
// 10. 如果 !reply.Success:
// a. (关键) 检查 rf.matchIndex[server] < args.PrevLogIndex,防止陈旧的失败回复
// b. 回退 rf.nextIndex[server]
// 11. 如果 reply.Success:
// a. (关键) 检查 newMatchIndex > rf.matchIndex[server],防止陈旧的成功回复
// b. 更新 rf.matchIndex[server] 和 rf.nextIndex[server]
// c. 检查是否可以更新 commitIndex (rf.updateCommitIndex())
// 12. 解锁
//
// replicateLogToPeer 负责向单个 peer 发送日志条目或心跳
func (rf *Raft) replicateLogToPeer(server int) {
rf.mu.Lock()
nextIdx := rf.nextIndex[server]
PrevLogIndex := nextIdx - 1
PrevLogTerm := rf.log[PrevLogIndex].Term
var entriesToSend []LogEntry
if len(rf.log) > nextIdx {
entriesToSend = rf.log[nextIdx:]
}
args := AppendEntriesArgs{
Term: rf.currentTerm,
LeaderId: rf.me,
Entries: entriesToSend, // 👈 只发送新的
PrevLogIndex: PrevLogIndex, // 👈 基于 nextIndex
PrevLogTerm: PrevLogTerm, // 👈 基于 PrevLogIndex
LeaderCommit: rf.commitIndex,
}
rf.mu.Unlock()
reply := AppendEntriesReply{}
ok := rf.peers[server].Call("Raft.AppendEntries", &args, &reply)
if !ok {
return
}
rf.mu.Lock()
defer rf.mu.Unlock()
if rf.role == Leader && rf.currentTerm == args.Term {
if reply.Term > rf.currentTerm {
rf.currentTerm = reply.Term
rf.role = Follower
rf.persist()
return
}
if !reply.Success {
if rf.matchIndex[server] < args.PrevLogIndex {
newNextIndex := reply.ConflictIndex
if reply.ConflictTerm != 0 {
// Case 2: Follower 报告了任期冲突
// Leader 在 *自己* 的日志中搜索这个任期
found := false
// (从后向前搜索,效率更高)
for i := len(rf.log) - 1; i > 0; i-- {
if rf.log[i].Term == reply.ConflictTerm {
// 找到了这个任期的 *最后一条* 日志
// 下次应该从这条日志的 *下一条* 开始
newNextIndex = i + 1
found = true
break
}
}
if !found {
// Case 3: Leader 没找到这个任期
// (意味着 Leader 也没有这个任期,直接使用 Follower 的 ConflictIndex)
newNextIndex = reply.ConflictIndex
}
}
// Case 1: (reply.ConflictTerm == 0)
// Follower 的日志太短了,直接使用 ConflictIndex
// (确保 nextIndex 至少为 1)
if newNextIndex < 1 {
newNextIndex = 1
}
rf.nextIndex[server] = newNextIndex
// -------------------------------------------------------------------
// (快速回退逻辑结束)
}
} else {
newMatchIndex := args.PrevLogIndex + len(args.Entries)
if newMatchIndex > rf.matchIndex[server] {
rf.matchIndex[server] = newMatchIndex
rf.nextIndex[server] = newMatchIndex + 1
//rf.updateCommitIndex()
rf.updateCommitIndexUnlocked()
}
}
}
}
func (rf *Raft) updateCommitIndexUnlocked() {
// (此函数假设调用者已持有锁)
// (这里没有 Lock/Unlock)
for N := len(rf.log) - 1; N > rf.commitIndex; N-- {
count := 1
for i := 0; i < len(rf.peers); i++ {
if i == rf.me {
continue
}
if rf.matchIndex[i] >= N {
count++
}
}
if count > len(rf.peers)/2 && rf.log[N].Term == rf.currentTerm {
rf.commitIndex = N
rf.applyCond1.Signal()
break
}
}
}
// Your code here (2B).
// (此函数必须在持有锁时调用)
// 1. 遍历 N from len(rf.log)-1 down to rf.commitIndex + 1
// 2. 检查 rf.log[N].Term == rf.currentTerm
// 3. 统计有多少 matchIndex[i] >= N (包括自己)
// 4. 如果 count > len(rf.peers)/2:
// a. rf.commitIndex = N
// b. 触发日志应用 (go rf.applyLogs())
// c. break
//
// updateCommitIndex 检查是否可以更新 Leader 的 commitIndex
func (rf *Raft) updateCommitIndex() {
rf.mu.Lock()
defer rf.mu.Unlock()
for N := len(rf.log) - 1; N > rf.commitIndex; N-- {
count := 1
for i := 0; i < len(rf.peers); i++ {
if i == rf.me {
continue
}
if rf.matchIndex[i] >= N {
count++
}
}
if count > len(rf.peers)/2 && rf.log[N].Term == rf.currentTerm {
rf.commitIndex = N
rf.applyCond1.Signal() // 或者 rf.applyCond.Signal()
break
}
}
}
// Your code here (2B).
// (此函数需要小心处理锁,一种常见模式是在循环开始前加锁获取
// commitIndex 和 lastApplied,在循环中发送消息,循环后再加锁更新 lastApplied)
//
// 简单(但效率较低)的模式:
// 1. 加锁
// 2. 循环 (for rf.lastApplied < rf.commitIndex)
// 3. rf.lastApplied++
// 4. 构造 ApplyMsg (msg.Command = rf.log[rf.lastApplied].Command)
// 5. 解锁
// 6. rf.applyCh <- msg (在锁外发送!)
// 7. 加锁 (为下一次循环)
// 8. 解锁
// applyLogs 将已提交的日志应用到状态机(通过 applyCh 发送)
func (rf *Raft) applierLoop() {
for !rf.killed() {
rf.mu.Lock()
for rf.lastApplied >= rf.commitIndex {
rf.applyCond1.Wait()
}
startIdx := rf.lastApplied + 1
endIdx := rf.commitIndex
logsToApply := make([]LogEntry, endIdx-startIdx+1)
copy(logsToApply, rf.log[startIdx:endIdx+1])
rf.lastApplied = rf.commitIndex
// 4. (修复了死锁) 释放锁!
rf.mu.Unlock()
// 5. (修复了死锁) 现在在 *锁外* 安全地发送消息
for i, entry := range logsToApply {
rf.applyCh <- ApplyMsg{
CommandValid: true,
Command: entry.Command,
CommandIndex: startIdx + i, // (修复了 ApplyMsg 逻辑)
}
}
}
}
// Make() -- 构造函数
func Make(peers []*labrpc.ClientEnd, me int,
persister *Persister, applyCh chan ApplyMsg) *Raft {
rf := &Raft{}
rf.peers = peers
rf.persister = persister
rf.me = me
rf.applyCond1 = sync.NewCond(&rf.mu)
// Your initialization code here (2A, 2B, 2C).
rf.role = Follower
rf.currentTerm = 0
rf.votedFor = -1
rf.log = make([]LogEntry, 1) // 日志索引从 1 开始
rf.log[0] = LogEntry{Term: 0, Command: nil} // 哨兵条目
rf.commitIndex = 0
rf.lastApplied = 0
rf.applyCh = applyCh
rf.heartbeatInterval = 100 * time.Millisecond // 示例
rf.lastHeartbeat = time.Now()
// (关键) 初始化一个随机的选举超时
rf.electionTimeout = time.Duration(300+rand.Intn(200)) * time.Millisecond
// initialize from state persisted before a crash
rf.readPersist(persister.ReadRaftState())
rf.nextIndex = make([]int, len(rf.peers))
rf.matchIndex = make([]int, len(rf.peers))
// 启动后台选举定时器
go rf.runElectionTimer()
go rf.applierLoop()
return rf
}
```