【ETCD】[源码阅读]深度解析 raft的 appendEntry方法

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)
}
  • 作用
    1. 将当前任期号(r.Term)赋值给每个日志条目。
    2. 为每个日志条目计算正确的索引号,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
}
  • 作用
    1. 调用 increaseUncommittedSize 方法,计算添加这些日志条目后,未提交日志的总大小是否超过限制。
    2. 如果超限,拒绝提议并返回 false
  • 背景
    • 未提交日志是指已经记录在领导者日志中但尚未被大多数节点确认的日志条目。
    • 为了避免无限制的日志增长导致内存溢出或性能下降,Raft 实现通常对未提交日志的总大小进行限制。
4. 追加日志条目
go 复制代码
li = r.raftLog.append(es...)
  • 作用
    1. 调用 r.raftLog.append 方法,将新的日志条目追加到领导者的日志中。
    2. 返回追加完成后的最后一个日志索引号。
  • 背景:Raft 协议要求领导者将客户端的提议日志条目追加到自己的日志中,并通过心跳或消息传播给其他节点。
5. 更新自身的日志复制进度
go 复制代码
r.prs.Progress[r.id].MaybeUpdate(li)
  • 作用
    1. 调用 MaybeUpdate 方法,更新领导者在 ProgressTracker 中自身的日志复制状态。
    2. 记录当前日志的最新索引号。
  • 背景ProgressTracker 是 Raft 实现中用来跟踪每个节点日志复制进度的核心结构。
6. 尝试提交日志
go 复制代码
r.maybeCommit()
  • 作用
    1. 检查是否可以推进已提交日志的索引号。
    2. Raft 协议规定,日志条目在被大多数节点复制后才能被认为是已提交的(committed)。
  • 背景
    • 已提交的日志条目可以被状态机应用。
    • maybeCommit 方法通过检查日志复制的状态来决定是否推进提交索引。
7. 返回成功
go 复制代码
return true
  • 作用:表示日志条目成功追加到领导者的日志中,并已触发相关操作(更新进度、尝试提交)。

总结

总的来说,该函数实现的代码如下:

  1. 日志条目的赋值与追加:为每个日志条目赋予正确的任期号和索引号,并将其追加到本地日志。
  2. 未提交日志大小限制:通过限制未提交日志的大小,防止系统内存溢出。
  3. 日志复制进度更新 :更新领导者在 ProgressTracker 中自身的日志复制状态。
  4. 推进日志提交:检查是否可以推进已提交日志索引,为状态机应用准备。
  5. 结果反馈 :返回日志条目追加成功与否的结果。
    r.raftLog.append(es...)后会使得n.rn.HasReady()能够返回true。
    这段代码是 Raft 协议中领导者日志管理的关键部分,它确保日志条目在本地日志中的正确性和有效性,同时通过限制未提交日志的大小来维持系统的稳定性。最终,它为后续的日志复制和提交提供了基础,是 Raft 协议实现中不可或缺的组成部分。
相关推荐
落笔画忧愁e30 分钟前
FastGPT快速将消息发送至飞书
服务器·数据库·飞书
Σίσυφος190044 分钟前
halcon 条形码、二维码识别、opencv识别
前端·数据库
小刘|1 小时前
深入理解 SQL 注入漏洞及解决方案
数据库·sql
天上掉下来个程小白2 小时前
案例-14.文件上传-简介
数据库·spring boot·后端·mybatis·状态模式
xidianjiapei0012 小时前
Kubernetes的Ingress 资源是什么?
云原生·容器·kubernetes
哆木2 小时前
排查生产sql查询缓慢
数据库·sql·mysql
企鹅侠客2 小时前
kube-proxy有什么作用?
云原生·kubelet
橘子师兄3 小时前
分页功能组件开发
数据库·python·django
book01214 小时前
MySql数据库运维学习笔记
运维·数据库·mysql
纠结哥_Shrek4 小时前
Oracle和Mysql的区别
数据库·mysql·oracle