【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 操作都会被同步到存储,并根据节点角色(领导者或跟随者)执行不同的行为。

相关推荐
愚润求学2 分钟前
【Linux】Linux权限
linux·服务器·语法
低头不见9 分钟前
一个服务器算分布式吗,分布式需要几个服务器
运维·服务器·分布式
小爬虫程序猿16 分钟前
淘宝商品信息如何存储到数据库?
数据库·爬虫·php
麻芝汤圆18 分钟前
使用 MapReduce 进行高效数据清洗:从理论到实践
大数据·linux·服务器·网络·数据库·windows·mapreduce
赋创小助手23 分钟前
Gartner预计2025年AI支出达6440亿美元:数据中心与服务器市场的关键驱动与挑战
运维·服务器·人工智能·科技·架构
靠近彗星1 小时前
如何检查 HBase Master 是否已完成初始化?| 详细排查指南
大数据·数据库·分布式·hbase
郑梓妍1 小时前
ubuntu改用户权限
服务器·网络·数据库
w23617346011 小时前
存储型XSS漏洞解析
数据库·oracle·xss·存储型xss
SelectDB2 小时前
Apache Doris 2025 Roadmap:构建 GenAI 时代实时高效统一的数据底座
大数据·数据库·aigc
用户6279947182622 小时前
GBase 8a南大通用数据库节点替换
数据库