核心问题
不可信环境下Follower不相信Leader持有的已达成共识日志CommitIndex, Leader不相信Follower已将分发的日志保存
在基本Raft中, Follower根据Leader发来的追加请求中, 要追加日志的前一条日志是否是自己持有的最新日志, 决定是否接受日志追加请求, BRaft中这个判断保持不变
在基本Raft中, Leader根据追加日志的反馈确认某条日志是否已经被半数以上Follower接受, 如果已经接收则更新自己的CommitIndex, 在下次发送追加请求时Follower根据Leader的CommitIndex将自己持有的日志应用到状态机
BRaft中Follower不能直接相信Leader的CommitIndex, 因此需要在Leader完成追加后发送一条AppendEntriesCommit广播, 告知其他节点某日志已被我添加记录到map, 当一条日志确实已被多数节点添加, 根据map记录推进CommitIndex的更新, 这对于Leader来说也一样, 因为不能相信Follower确实添加了日志, 也就不能直接更新CommitIndex
修改LogEntry结构体, 增加每条记录的签名
type LogEntry struct {
Term int // the log entry's term
CommandValid bool // if it should be applied
Command interface{} // 操作日志 the command should be applied to the state machine
Signature []byte
}
在AppendEntries中,Follower要校验Leader发来的日志是否与签名一致, 如果发现不一致说明被Leader篡改, 立即重新发起选举, 目前代码只校验了一条, 本版代码做了多条日志确认的优化, 否则无法通过PartB测试
if !verifySignature(GetBytes(args.Entries[0].Command), args.Entries[0].Signature) {
rf.becomeCandidateLocked()
return
}
Follower收到追加日志之后并不信任Leader确实已经持有了自己已提交的日志, 因此发起一轮确认广播, 在所有节点之间再次确定Leader发送的日志是正确的, 需要两个新的结构体
type AppendEntriesCommitArgs struct {
Term int
PeerId int // 确认消息发送者Id
Signature []byte // PeerId 的数字签名,用于防止拜占庭节点伪造确认消息
EntryIndex int
EntryTerm int
EntryHash string
}
type AppendEntriesCommitReply struct {
Term int
Success bool
}
为了让Follower能够自行统计Committed日志的数量, 新增一个raft_map文件, 在Raft结构体里增加一个map, 用于各个节点自行统计被提交的日志
package raft
import (
"fmt"
"sort"
)
type AppendEntriesCommitKey struct {
Term int
Index int
Hash string
}
type CommitKeySlice []AppendEntriesCommitKey
//Raft
m map[AppendEntriesCommitKey]int
//Make
rf.m = make(map[AppendEntriesCommitKey]int)
在AppendEntries中增加broadcastAppendEntriesCommit(rf, args.PrevLogIndex, args.PrevLogTerm)
func broadcastAppendEntriesCommit(rf *Raft, index int, term int) {
for i := range rf.peers {
if i != rf.me && rf.role != Candidate {
args := AppendEntriesCommitArgs{Term: rf.currentTerm, PeerId: rf.me, EntryIndex: index, EntryTerm: term}
reply := AppendEntriesCommitReply{}
go func(server int) {
rf.sendAppendEntriesCommit(server, args, &reply)
}(i)
}
}
}
func (rf *Raft) sendAppendEntriesCommit(server int, args AppendEntriesCommitArgs, reply *AppendEntriesCommitReply) bool {
ok := rf.peers[server].Call("Raft.AppendEntriesCommit", args, reply)
return ok
}
func (rf *Raft) AppendEntriesCommit(args AppendEntriesCommitArgs, reply *AppendEntriesCommitReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
key := AppendEntriesCommitKey{Term: args.EntryTerm, Index: args.EntryIndex}
_, ok := rf.m[key]
if ok == true {
rf.m[key] += 1
} else {
rf.m[key] = 1
}
if rf.m[key] >= len(rf.peers)/2 && key.Index > rf.commitIndex {
rf.commitIndex = key.Index
}
reply.Success = true
}
目前完成日志添加的操作, 接下来需要完成Leader对共识记录的更新, 各节点对共识记录的应用, AppendEntriesCommit增加签名验证
增加了一轮广播后On^2复杂度, 性能大幅下降, PartB原本的一些测试会无法通过