【ETCD】【源码阅读】深入解析 raftNode.start方法实现

让我们从源码层面逐步分析这段代码。这段代码是 ETCD Raft 实现中,raftNodestart 方法,负责启动一个 Raft 节点,并在一个新的 goroutine 中处理 Raft 相关的事件。以下是对代码的逐步分析:

代码总体结构

go 复制代码
func (r *raftNode) start(rh *raftReadyHandler) {
	internalTimeout := time.Second

	go func() {
		defer r.onStop()  // 确保 goroutine 停止时调用 onStop 方法

		islead := false  // 用来记录节点是否是领导者

		for {
			select {
			case <-r.ticker.C:
				r.tick()  // 定时器事件,触发 tick 操作
			case rd := <-r.Ready():  // 等待 Raft 操作准备好
				// 处理 Raft 操作
			case <-r.stopped:  // 如果 raftNode 被停止
				return
			}
		}
	}()
}

1. 启动 Goroutine

go 复制代码
go func() {
	defer r.onStop()
  • 这部分代码使用 go 关键字在一个新的 goroutine 中异步执行 Raft 节点的操作。defer r.onStop() 确保在 goroutine 停止时执行 onStop() 方法进行清理。

2. 初始化领导者标志

go 复制代码
islead := false
  • 这行代码用于初始化 islead 标志,记录当前节点是否是 Raft 集群中的领导者。

3. 事件循环

go 复制代码
for {
    select {
    case <-r.ticker.C:
        r.tick()  // 每当 ticker 定时器触发时,执行 tick 操作
    case rd := <-r.Ready():  // 获取 Raft 准备好的日志条目
        // 处理 Raft 准备好的日志条目
    case <-r.stopped:  // 监视是否收到停止信号
        return
    }
}
  • 这部分是事件循环,select 用来监听多个事件:
    • r.ticker.C:定时器事件,用于执行 tick 操作。
    • r.Ready():从 Raft 引擎获取准备好的日志条目并处理。
    • r.stopped:接收到停止信号时退出 goroutine。

4. 处理 Raft Ready 数据

go 复制代码
case rd := <-r.Ready():
    if rd.SoftState != nil {
        newLeader := rd.SoftState.Lead != raft.None && rh.getLead() != rd.SoftState.Lead
        if newLeader {
            leaderChanges.Inc()  // 记录领导者变更
        }

        if rd.SoftState.Lead == raft.None {
            hasLeader.Set(0)
        } else {
            hasLeader.Set(1)
        }

        rh.updateLead(rd.SoftState.Lead)
        islead = rd.RaftState == raft.StateLeader
        if islead {
            isLeader.Set(1)
        } else {
            isLeader.Set(0)
        }
        rh.updateLeadership(newLeader)
        r.td.Reset()
    }
  • rd := <-r.Ready():从 Raft 中读取准备好的日志条目和状态。
  • 通过 rd.SoftState 判断是否发生了领导者变更,并更新领导者相关的信息(例如,islead 是否为领导者)。
  • leaderChanges.Inc():增加领导者变更计数。
  • hasLeader.Set()isLeader.Set():分别更新是否存在领导者以及当前节点是否是领导者的状态。
  • rh.updateLead()rh.updateLeadership():更新领导者信息。
  • r.td.Reset():重置一个定时器。

5. 处理 Raft ReadState

go 复制代码
if len(rd.ReadStates) != 0 {
    select {
    case r.readStateC <- rd.ReadStates[len(rd.ReadStates)-1]:
    case <-time.After(internalTimeout):
        r.lg.Warn("timed out sending read state", zap.Duration("timeout", internalTimeout))
    case <-r.stopped:
        return
    }
}
  • 处理 ReadState,如果有待处理的读取请求,尝试将其发送到 r.readStateC 信道。如果在 internalTimeout 时间内没有完成,则会记录警告日志。

6. 应用日志条目和快照

go 复制代码
notifyc := make(chan struct{}, 1)
ap := apply{
    entries:  rd.CommittedEntries,
    snapshot: rd.Snapshot,
    notifyc:  notifyc,
}
updateCommittedIndex(&ap, rh)

waitWALSync := shouldWaitWALSync(rd)
if waitWALSync {
    if err := r.storage.Save(rd.HardState, rd.Entries); err != nil {
        r.lg.Fatal("failed to save Raft hard state and entries", zap.Error(err))
    }
}
  • 创建一个 apply 对象,表示已提交的日志条目和快照。
  • updateCommittedIndex(&ap, rh):更新已提交的日志索引。
  • shouldWaitWALSync(rd):判断是否需要等待 WAL(Write Ahead Log)同步。
  • 如果需要同步,则调用 r.storage.Save() 将硬状态和日志条目保存到存储中。

7. 处理领导者和快照

go 复制代码
if islead {
    r.transport.Send(r.processMessages(rd.Messages))  // 领导者处理消息并发送
}

if !raft.IsEmptySnap(rd.Snapshot) {
    if err := r.storage.SaveSnap(rd.Snapshot); err != nil {
        r.lg.Fatal("failed to save Raft snapshot", zap.Error(err))
    }
}

if !waitWALSync {
    if err := r.storage.Save(rd.HardState, rd.Entries); err != nil {
        r.lg.Fatal("failed to save Raft hard state and entries", zap.Error(err))
    }
}
  • 如果当前节点是领导者(islead),则处理 Raft 消息并发送。
  • 如果有快照,保存快照数据 (r.storage.SaveSnap(rd.Snapshot)),确保恢复后能够正确加载快照。

8. 同步存储和释放资源

go 复制代码
if err := r.storage.Sync(); err != nil {
    r.lg.Fatal("failed to sync Raft snapshot", zap.Error(err))
}

notifyc <- struct{}{}
r.raftStorage.ApplySnapshot(rd.Snapshot)
r.lg.Info("applied incoming Raft snapshot", zap.Uint64("snapshot-index", rd.Snapshot.Metadata.Index))
if err := r.storage.Release(rd.Snapshot); err != nil {
    r.lg.Fatal("failed to release Raft wal", zap.Error(err))
}
  • 调用 r.storage.Sync() 确保存储同步。
  • 应用快照 (r.raftStorage.ApplySnapshot(rd.Snapshot)) 并记录日志。
  • 释放存储中的快照资源 (r.storage.Release(rd.Snapshot)).

9. 将日志条目添加到存储中

go 复制代码
r.raftStorage.Append(rd.Entries)
  • 将 Raft 条目追加到存储中。

10. 处理非领导者节点的消息

go 复制代码
if !islead {
    msgs := r.processMessages(rd.Messages)
    notifyc <- struct{}{}
    select {
    case notifyc <- struct{}{}:
    case <-r.stopped:
        return
    }
    r.transport.Send(msgs)
} else {
    notifyc <- struct{}{}
}
  • 如果当前节点不是领导者(!islead),则处理 Raft 消息,并发送到网络。
  • 其他情况(领导者节点)发送通知。

11. 推进 Raft 状态

go 复制代码
r.Advance()
  • 调用 r.Advance(),推动 Raft 状态,更新日志。

总结

此方法用于启动 Raft 节点并在 goroutine 中异步运行 Raft 状态机。它通过定时器定期触发 tick 操作,并通过 Ready() 方法获取 Raft 的操作数据进行处理。它处理日志条目、快照、领导者变更等 Raft 相关的核心操作。每个 Raft 操作都会被同步到存储,并根据节点角色(领导者或跟随者)执行不同的行为。

相关推荐
凌辰揽月几秒前
Web后端基础(基础知识)
java·开发语言·前端·数据库·学习·算法
想你依然心痛5 分钟前
数据库入门:从原理到应用
数据库
知之则吱吱44 分钟前
亚马逊AWS云服务器高效使用指南:最大限度降低成本的实战策略
服务器·云计算·aws
cui_win1 小时前
每日一令:Linux 极简通关指南 - 汇总
linux·运维·服务器
知星小度S1 小时前
Linux权限探秘:驾驭权限模型,筑牢系统安全
linux·运维·服务器
20242817李臻2 小时前
20242817李臻-安全文件传输系统-项目验收
数据库·安全
行思理2 小时前
MongoDB慢查询临时开启方法讲解
数据库·mongodb
bbsh20992 小时前
WebFuture 升级提示“不能同时包含聚集KEY和大字段””的处理办法
数据库·sql·mysql·webfuture
Kaede64 小时前
如何应对Linux云服务器磁盘空间不足的情况
linux·运维·服务器
Zfox_6 小时前
Redis:Hash数据类型
服务器·数据库·redis·缓存·微服务·哈希算法