mit6.824 lab2实验记录

61 阅读12分钟

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: 立即初始化 nextIndexmatchIndex 数组,并启动 go rf.runLeaderLoop() 开始持续发送心跳。
  • 失败 (Split Vote): 选举超时,runElectionTimer 会再次触发,startElection 会递增 currentTerm 并开始新一轮选举。
  • 失败 (发现新 Leader): AppendEntries RPC 会使 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 复制日志 (runLeaderLoopreplicateLogToPeer):

    • runLeaderLoop 周期性(heartbeatInterval)地为每个 peer 调用 go rf.replicateLogToPeer

    • replicateLogToPeer 是日志复制的核心:

      1. 准备 AppendEntriesArgs Leader 根据 rf.nextIndex[server] 确定 PrevLogIndexPrevLogTerm,并附上 rf.log[nextIdx:] 的所有条目(如果 entriesToSend 为空,这就是一个心跳)。

      2. 发送 RPC

      3. 处理回复 (关键):

        • reply.Success == true (成功): 更新 rf.matchIndex[server]rf.nextIndex[server]。然后调用 rf.updateCommitIndexUnlocked() 尝试推进 commitIndex

        • reply.Success == false (失败): 这是 Follower 日志不一致。你的实现包含了 "快速回退" 优化:

          • Follower 在 AppendEntries 中返回了 ConflictTermConflictIndex

          • Leader 使用这些信息:

            • 如果 ConflictTerm 存在于 Leader 的日志中,Leader 会找到自己日志中该任期的 最后一条 记录,并设置 newNextIndex = i + 1
            • 如果 ConflictTerm 不存在,Leader 直接设置 newNextIndex = reply.ConflictIndex
          • 这比每次只回退 1 个索引 (nextIndex--) 要高效得多。

  • Follower 接受日志 (AppendEntries RPC):

    • 一致性检查:

      1. args.PrevLogIndex > len(rf.log)-1 (日志太短):拒绝,返回 ConflictIndex = len(rf.log)
      2. rf.log[args.PrevLogIndex].Term != args.PrevLogTerm (任期冲突):拒绝,返回 ConflictTerm = rf.log[args.PrevLogIndex].Term 和该任期的 第一个 索引 ConflictIndex
    • 接受日志:

      1. 处理冲突: 你的代码使用 sameIndex 查找第一个不匹配的条目,然后使用 rf.log = append(rf.log[:sameIndex+args.PrevLogIndex], args.Entries[sameIndex-1:]...)截断 旧的冲突日志并追加新日志。这是处理 Follower 日志与 Leader 不一致的标准方法。
      2. 持久化: rf.persist()
      3. 更新 Commit: rf.commitIndex = min(len(rf.log)-1, args.LeaderCommit)
      4. 唤醒 Applier: rf.applyCond1.Signal()
  • 提交与应用 (updateCommitIndexUnlockedapplierLoop):

    • Leader 提交:updateCommitIndexUnlocked 中,Leader 查找一个 N > rf.commitIndex,如果 rf.log[N].Term == rf.currentTermmatchIndex 中有 过半数 成员 matchIndex[i] >= N,Leader 就将 rf.commitIndex 更新为 N

    • 应用到状态机: applierLoop (goroutine) 负责应用。

      1. 它使用 sync.Cond (rf.applyCond1.Wait()) 高效地等待,直到 rf.commitIndex > rf.lastApplied
      2. (关键)防止死锁: 你的实现非常棒。它在持有锁时,将 rf.log[startIdx:endIdx+1] 复制到局部变量 logsToApply 中,然后释放锁 (rf.mu.Unlock())。
      3. 最后,它在 锁外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(&currentTerm) != 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
}
```