mit 6.824 lab2B,raft日志复制(lab2D中有关于此处大量代码修改找出了很多错误)

lab2 说明:

https://pdos.csail.mit.edu/6.824/labs/lab-raft.html

参考博客:

https://zhuanlan.zhihu.com/p/514512060

https://blog.csdn.net/weixin_45938441/article/details/124797074?spm=1001.2014.3001.5502

实现内容:

实现领导者和追随者代码以附加新的日志条目。

 

主要函数   FOLLOWER :AppendEntries 心跳和数据同步

     LEADER:   start 添加条目、 心跳发送及 response 相关函数

     所有服务器:apply  条目提交

 

 

理解 test 的目的对于调试很重要:

 

 上图摘自csdn博客


 日志复制流程

  1、客户端通过start函数向Leader提交数据。

  2、Leader 根据nextIndex数组发送RPC请求调用每个FOLLOWER的AppendEntries 复制条目。

  3、FOLLOWER处理条目实现mit6.824优化,返回reply。

  4、Leader 处理reply ,计算每个FOLLOWER新的nextIndex 和 matchIndex

  5、Leader 根据matchIndex 计算LeaderCommit

  6、Leader 提交条目。

  7、Leader 发送心跳包含LeaderCommit,FOLLOWER提交条目。


 

 start函数要注意使用锁,保证提交顺序 

requestAppends 函数是发送RPC请求, false 代表不是心跳。

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
unc (rf *Raft) Start (command interface{}) (int, int, bool) {
    rf.mu.Lock()
    defer rf.mu.Unlock()
    index := -1
    term := -1
    isLeader := true
 
    // Your code here (2B).
    if rf.status != Leader {
        return index, term, false
    }
 
    term = rf.currentTerm
    index = rf.start(command)
    isLeader = true
 
    DDPrintf("s:%v  t:%v  写入数据:%v",rf.me,rf.currentTerm,command)
    rf.requestAppends(false)
    return index, term, isLeader
}
 
//添加请求到log
//返回index
func (rf *Raft) start (command interface{}) int {
    new := Entry {
        Value: command,
        Term:  rf.currentTerm,
        Index: 0,
    }
 
    return rf.log.append(new)
}

  


 

 

Entries包含next位置及其后面所有条目,基本都是按论文里实现的。

 

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
func (rf *Raft) appendEntrier (isHeart bool,peer int) {
    next := rf.nextIndex[peer]
    if next < rf.log.Index0 {
        //next最小为快照位置
        next = rf.log.Index0 + 1
    }
 
    args := RequestAppendArgs {
        Term:         rf.currentTerm,
        Leaderld:     rf.me,
        PrelogIndex:  next - 1,
        PrelogTerm:   rf.log.getLogITerm(next - 1),
        Entries:      make([]Entry,rf.log.lastlogIndex() - next + 1),
        LeaderCommit: rf.commitIndex,
    }
 
    copy(args.Entries,rf.log.slice(next))
    if isHeart {
        DDPrintf("s:%v t:%v  发送心跳到 s:%v  value: %v",rf.me,rf.currentTerm,peer,args.Entries)
    }else {
        DDPrintf("s:%v t:%v  发送数据到 s:%v  value:%v",rf.me,rf.currentTerm,peer,args.Entries)
    }
    go func() {
        var reply ResponseAppendArgs
        ok := rf.sendAppendEntries(peer,&args,&reply)
        if ok {
            rf.mu.Lock()
            defer rf.mu.Unlock()
            rf.appendSubsequentTreatmentL(peer,args,reply)
        }
    }()
}

  

 


 

这里看FOLLOWER处理 AppendEnies

 

 代码的作用

  ①  非Follow 收到心跳可能是有一个服务器断线重连,不管接受者服务器是不是leader只要收到AppendEntries 就切换为follower 并且集群重新开始选举出合适的。

  ②   注意重复写入问题,同样的AppendEntries 可能由于网络原因重复接收到,所以要进行重复写入判断,我这里的判断代码非常粗暴。

  ③  剩下几段代码是根据mit 6.824 快速恢复小节实现的优化

  ④ 追加数据后通过Cond 信号进行提交

 

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
64
65
66
67
//心跳、追加日志
func (rf *Raft) AppendEntries (args *RequestAppendArgs, reply *ResponseAppendArgs) {
 
    rf.mu.Lock()
    defer rf.mu.Unlock()
 
    if rf.currentTerm > args.Term {
        reply.Term = rf.currentTerm
        return
    }
 
    //非follow 收到心跳
    if rf.status != Followers && rf.currentTerm <= args.Term {
        rf.newTerm(args.Term)
    }
 
    rf.setElectionTime()
 
 
    //重复写入
    Elenght := len(args.Entries)
    if Elenght != 0 && rf.log.lastlogIndex() == args.Entries[Elenght - 1].Index && rf.log.lastLogTerm() == args.Entries[Elenght - 1].Term{
        reply.Success = true
        return
    }
 
 
    //follow prelogIndex缺失
    if rf.log.lastlogIndex() < args.PrelogIndex {
        reply.Term = rf.currentTerm
        reply.XLen = args.PrelogIndex - rf.log.lastlogIndex()
        reply.XTerm = -1
        reply.XIndex = rf.log.lastlogIndex()
        reply.Success = false
        DDPrintf("s:%v t:%v  缺失数据 entries:%v     来自s:%v t:%v entries:%v",rf.me,rf.currentTerm,rf.log.Entrys,args.Leaderld,args.Term,args.Entries)
        return
    }
 
    //prelogIndex处term不匹配
    if args.PrelogTerm != rf.log.getLogITerm(args.PrelogIndex) {
        reply.Success = false
        reply.Term = rf.currentTerm
        reply.XIndex = rf.findStartTermIndex(args.PrelogIndex)
        reply.XTerm = rf.log.getLogITerm(args.PrelogIndex)
        rf.log.delSlice(reply.XIndex)
        DDPrintf("s:%v t:%v  不匹配 entries:%v     来自s:%v t:%v entries:%v   prelogIndex:%v",rf.me,rf.currentTerm,rf.log.Entrys,args.Leaderld,args.Term,args.Entries,args.PrelogIndex)
        return
    }
 
    //追加
    if len(args.Entries) == 0 {
        reply.Success = true
        if rf.commitIndex < args.LeaderCommit {
            rf.commitIndex = args.LeaderCommit
        }
        rf.applyCond.Broadcast()
        DDPrintf("s:%v term:%v 接收心跳  来自 s:%v  term:%v\n", rf.me, rf.currentTerm, args.Leaderld, args.Term)
        return
    }
 
    rf.log.theIndexAppend(args.PrelogIndex+1, args.Entries)
    DDPrintf("s:%v   追加数据 data:%v",rf.me,args.Entries)
    reply.Success = true
 
    rf.commitIndex = rf.min(args.LeaderCommit,args.Entries[Elenght - 1].Index)
    rf.applyCond.Broadcast()
}

  


 

 

①根据响应结果计算 新的nextIndex 和matchIndex

②根据matchIndex计算leaderCommit

③leaderCommit 可能有变化所以提交一次条目

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
64
65
66
67
68
69
70
71
72
73
74
75
76
//leader发送心跳响应的后续处理
func (rf *Raft) appendSubsequentTreatmentL (peer int,args RequestAppendArgs,reply ResponseAppendArgs) {
    if reply.Term > rf.currentTerm {
        rf.newTerm(reply.Term)
        return
        //当前服务器没有从leader变为follow
    else if rf.currentTerm == args.Term {
        rf.appendEntriesResponse(peer,args,reply)
    }
}
 
func (rf *Raft) appendEntriesResponse (peer int,args RequestAppendArgs , reply ResponseAppendArgs ) {
 
    if !reply.Success {
        //缺失
        if reply.XTerm == -1 {
            rf.nextIndex[peer] -= reply.XLen
            return
        }
        //leader不存在term
        if i := rf.findlastTermIndex(args.PrelogIndex,reply.Term) ; i == -1 {
            rf.nextIndex[peer] = reply.XIndex
            return
        } else {
            //leader 与 follow不同步
            rf.nextIndex[peer] = i
            return
        }
    }
 
    if len(args.Entries) == 0 {  //心跳不用更新
        return
    }
 
    newnext := args.PrelogIndex + len(args.Entries) + 1
    newmatch := args.PrelogIndex + len(args.Entries)
 
    if newnext > rf.nextIndex[peer] {
        rf.nextIndex[peer] = newnext
    }
    if newmatch > rf.matchIndex[peer] {
        rf.matchIndex[peer] = newmatch
    }
 
    rf.inspectCommItIndex()
}
 
//检查leader commitIndex并重设
//如果存在一个满足N > commitIndex的 N,并且大多数的matchIndex[i] ≥ N成立,并且log[N].term == currentTerm成立,那么令 commitIndex = N (5.3 和 5.4 节)
func (rf *Raft)inspectCommItIndex() {
    for peer,oneMatchIndex := range rf.matchIndex {
        if peer == rf.me {
            continue
        }
 
        if oneMatchIndex > rf.commitIndex {
            num := 1
            for PEER,otherMatchIndex := range rf.matchIndex {
                if PEER == rf.me || peer == PEER {
                    continue
                }
 
                if oneMatchIndex <= otherMatchIndex {
                    num += 1
                }
            }
 
            if num >= len(rf.matchIndex) / 2 {
                rf.commitIndex = oneMatchIndex
                rf.matchIndex[rf.me] = oneMatchIndex
            }
 
        }
    }
    rf.applyCond.Broadcast()
}

  


 

条目提交

使用管道可能有死锁,这是mit6.824中提到的,要在管道处取消锁。

 

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
func (rf *Raft) kvServerLayer () {
    rf.mu.Lock()
    defer rf.mu.Unlock()
    for {
        if rf.commitIndex >= rf.lastApplied + 1 &&
            rf.lastApplied + 1 <= rf.log.lastlogIndex() &&
            rf.lastApplied + 1 > rf.log.start(){
            rf.lastApplied ++
            nMsg := ApplyMsg{
                CommandValid:  true,
                Command:       rf.log.Entrys[rf.lastApplied].Value,
                CommandIndex:  rf.log.Entrys[rf.lastApplied].Index,
                SnapshotValid: false,
                Snapshot:      nil,
                SnapshotTerm:  0,
                SnapshotIndex: 0,
            }
            rf.mu.Unlock()
            rf.ApplyC <- nMsg
            rf.mu.Lock()
        } else {
            rf.applyCond.Wait()
        }
    }
}

  

 

 

做的时候要多分析log,通过log可以看出很多问题,这些测试可以一个一个做。

 

posted on   thotf  阅读(406)  评论(1编辑  收藏  举报

相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本

导航

< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5
点击右上角即可分享
微信分享提示