java
func (r *raft) appendEntry(es ...pb.Entry) (accepted bool) {
li := r.raftLog.lastIndex()
for i := range es {
es[i].Term = r.Term
es[i].Index = li + 1 + uint64(i)
}
// Track the size of this uncommitted proposal.
if !r.increaseUncommittedSize(es) {
r.logger.Debugf(
"%x appending new entries to log would exceed uncommitted entry size limit; dropping proposal",
r.id,
)
// Drop the proposal.
return false
}
// use latest "last" index after truncate/append
li = r.raftLog.append(es...)
r.prs.Progress[r.id].MaybeUpdate(li)
// Regardless of maybeCommit's return, our caller will call bcastAppend.
r.maybeCommit()
return true
}
源码分析
1. 获取当前日志的最后索引
go
li := r.raftLog.lastIndex()
- 作用:获取当前日志的最后一个索引号,用于计算新日志条目的索引。
- 背景:Raft 协议中的日志条目是按顺序存储的,每个条目都有唯一的索引号,索引号从 1 开始递增。
2. 为每个新日志条目赋值
go
for i := range es {
es[i].Term = r.Term
es[i].Index = li + 1 + uint64(i)
}
- 作用 :
- 将当前任期号(
r.Term
)赋值给每个日志条目。 - 为每个日志条目计算正确的索引号,
li + 1 + uint64(i)
。
- 将当前任期号(
- 背景:Raft 协议要求每个日志条目必须有明确的任期号和索引号,任期号标识领导者的任期,索引号标识日志在整个日志序列中的位置。
3. 检查未提交日志的大小是否超限
go
if !r.increaseUncommittedSize(es) {
r.logger.Debugf(
"%x appending new entries to log would exceed uncommitted entry size limit; dropping proposal",
r.id,
)
// Drop the proposal.
return false
}
- 作用 :
- 调用
increaseUncommittedSize
方法,计算添加这些日志条目后,未提交日志的总大小是否超过限制。 - 如果超限,拒绝提议并返回
false
。
- 调用
- 背景 :
- 未提交日志是指已经记录在领导者日志中但尚未被大多数节点确认的日志条目。
- 为了避免无限制的日志增长导致内存溢出或性能下降,Raft 实现通常对未提交日志的总大小进行限制。
4. 追加日志条目
go
li = r.raftLog.append(es...)
- 作用 :
- 调用
r.raftLog.append
方法,将新的日志条目追加到领导者的日志中。 - 返回追加完成后的最后一个日志索引号。
- 调用
- 背景:Raft 协议要求领导者将客户端的提议日志条目追加到自己的日志中,并通过心跳或消息传播给其他节点。
5. 更新自身的日志复制进度
go
r.prs.Progress[r.id].MaybeUpdate(li)
- 作用 :
- 调用
MaybeUpdate
方法,更新领导者在ProgressTracker
中自身的日志复制状态。 - 记录当前日志的最新索引号。
- 调用
- 背景 :
ProgressTracker
是 Raft 实现中用来跟踪每个节点日志复制进度的核心结构。
6. 尝试提交日志
go
r.maybeCommit()
- 作用 :
- 检查是否可以推进已提交日志的索引号。
- Raft 协议规定,日志条目在被大多数节点复制后才能被认为是已提交的(
committed
)。
- 背景 :
- 已提交的日志条目可以被状态机应用。
maybeCommit
方法通过检查日志复制的状态来决定是否推进提交索引。
7. 返回成功
go
return true
- 作用:表示日志条目成功追加到领导者的日志中,并已触发相关操作(更新进度、尝试提交)。
总结
总的来说,该函数实现的代码如下:
- 日志条目的赋值与追加:为每个日志条目赋予正确的任期号和索引号,并将其追加到本地日志。
- 未提交日志大小限制:通过限制未提交日志的大小,防止系统内存溢出。
- 日志复制进度更新 :更新领导者在
ProgressTracker
中自身的日志复制状态。 - 推进日志提交:检查是否可以推进已提交日志索引,为状态机应用准备。
- 结果反馈 :返回日志条目追加成功与否的结果。
r.raftLog.append(es...)后会使得n.rn.HasReady()能够返回true。
这段代码是 Raft 协议中领导者日志管理的关键部分,它确保日志条目在本地日志中的正确性和有效性,同时通过限制未提交日志的大小来维持系统的稳定性。最终,它为后续的日志复制和提交提供了基础,是 Raft 协议实现中不可或缺的组成部分。