Raft Scheduler 概述
Raft Scheduler 在节点初始化时会一起启动,同时还会一同启动 raftTickLoop 进程用于定时产生 tick 请求。
Raft Scheduler 主要的功能是处理 raftGroup 内部的消息请求与外部的写入读取请求,Raft Scheduler 的工作方式是通过对外提供一个 rangeID 队列,当有请求来时,会修改该请求设计的 range 状态,并将其放入这个队列中,同时当 Raft Scheduler 跟随 KaiwuDB 启动而初始化时,会初始化一定数量的 processor,这些 processor 的作用是监听消息队列,当消息队列中有 rangeID 入队时,processor 会将该 rangeID 取出,并根据该 rangeID 入队时的状态 (stateTick、stateReady、stateRequest) 进行不同处理。
我们在后续优化 KaiwuDB 版本的过程中将 RaftScheduler 额外划分了一部分资源用于处理 tick 请求从而降低心跳延迟,这并不影响我们理解整体的逻辑处理流程。
Request 入队流程
在副本层中需要处理的数据来自于分发层通过 gRPC 发送来的 RaftMessageBatch,以及内部产生的消息请求。
当接收到分发层的 RaftMessageBatch 后,消息处理会通过 store 获取 RaftMessageRequest 对应 rangeID 的 raftRequestQueue,并将 RaftMessageRequest 转换成 raftRequestInfo 追加至 raftRequestQueue 中。与此同时,更新 rangeID 对应的 raftScheduleState 并将 rangeID 添加到 raftScheduler 的 queue 中等待 raft processor 调度处理。
StateRaftTick 处理流程
如果 processor 从 rangeIDQueue 中取出的 rangeID 对应状态为 stateRaftTick,则 processor 会调用 ProcessTick () 方法进行处理。
首先,processor 会在 store 中获取该 rangeID 的本地 replica 和对应 livenessMap, 然后调用 replica_raft.go 下 tick ()。获取 replica 的 unreachable remotes,构造 MsgUnreachable, 通过 replica 所在的 raft group 处理该 msg。结束这一步后,会验证 replica 是否为静默状态,如果 replica 是静默状态,则无需 tick。Tick 的条件为:
A. raft group 已初始化。
B. replica 是非静默的。
C. 再次检查 replica 是否静默,静默的判断条件为满足以下所有条件:
- cluster 启用静默配置。
- replica 不存在尚未应用的 raft command。
- replica 不存在进行中的 merge。
- replica 没有被 destroyed。
- replica 所在 raft group 状态非空。
- replica 是 raft leader。
- replica 当前不存在 raft leader 切换。
- replica 是 leaseholder。
- raft 的 applied/commit/lastindex 相等。
- 获取所有 remote replica,除了不在 livenessMap 中的 node,remote replica 在 leader 生成的 progress 列表中,同时 remote replica 的 progress match 值必须跟当前 raft 的 applied 相等。
- replica 本身必须在 progress 列表中。
- raft 当前状态不是 ready。
如果判断当前 replica 所在 raft group 是静默状态,只发送心跳数据:
首先,获取 range 中其他 remote replica 的 ReplicaDescriptor,为每个 remote replica 构造 raftpb.MsgHeartbeat, 构造 StoreIdent 和 RaftHeartbeat, 生成 KV 并写入 store 的 heartbeats map <目标 store 标识,心跳数据> 中,在 store.go->coalescedHeartbeatsLoop () 处理 store 的心跳数据,对每个 KV 构造 RaftMessageRequest 并写入对应节点的 channel 中,在 raft_transport.go->startProcessNewQueue ()->processQueue () 中维护该 channel,负责将 request 转发到指定 replica。
如果判断当前 replica 所在 raft group 是非静默状态,除进行上述操作外,在这之后会检查当前 replica 是否同时为 raft leader 和 leaseholder (DisableLeaderFollowsLeaseholder 为 false 时的要求),如果不满足,会构造 leader 切换的 message,发起一轮选举。
- StateRaftReady -
如果 rangeID 对应的状态是 stateRaftReady。
- store.go->processReady () 首先从 store 中获取该 rangeID 对应的本地 replica,通过 relica 的 internalRaftGroup 构造 raft Ready 数据 (raft Ready 包含了需要持久化的 entries 和需要提交或发送到其他节点的 message),然后将 Ready 中包含的 msg 分为 MsgApp 和其他类型两部分,首先将 MsgApp 类型消息构造成 RaftMessageRequest 写入到对应 node 的 channel 中,然后通过 raft_transport.go->startProcessNewQueue ()->processQueue () 转发到指定 replica。
- 在异步发送 msg 过程中,会通过 rocksDB/Pebble 构造 batch,同时处理 Ready 中包含的待持久化的 entries,并构造 repr,然后使用构造的 batch 将 repr 异步迭代写入到 store 对应的存储引擎中。
- 将拆分出的其他消息类型构造 RaftMessageRequest 发送到 node 的 channel 中。
- 从 Ready 中包含的 CommittedEntries 每一行中获取 command id 和 raft command 并生成 WriteBatch, 构造 WriteBatch, 并生成 Repr,将 Repr 中数据异步落盘。
- StateRaftRequest -
如果 rangeID 对应的状态是 stateRaftRequest,从 store 中获取该 rangeID 对应的 raftRequestQueue,然后获取队列中的每个 raftRequestInfo, 每个 raftRequestInfo 中的 RaftMessageRequest 都带有一条 message,根据每条 message 的 type 不同有不同的处理方法,可参考上述 Msg 类型篇。
- MsgHub,当 follower 节点的选举计时器超时后,会发送 msgHub.
- MsgBeat,leader 发送的心跳信息,心跳计时器超时时触发该消息,leader 通过 stepLeader () 生成 MsgHeartbeat 发送给集群中其他节点。
- MsgProp,客户端向集群发送的写请求通过 msgProp 表示。
- MsgApp,当一个节点通过选举成为 leader 后,会获取目标节点 Next-1 对应的记录的 term 值和需要发送的 Entries, 然后发送 MsgApp 消息,该消息可以帮助 follower 节点与 leader 节点同步。
- MsgAppResp,msgApp 的响应消息类型,当 follower 节点收到 msgApp 后,无论是否进行日志追加,都将返回一条带有本节点最后一条记录索引值的消息。
- MsgVote,当 PreCandidate 状态节点收到半数以上的投票之后,会发起新一轮的选举,即向集群中的其他节点发送 MsgVote。
- MsgVoteResp,msgVote 的响应消息。
- MsgSnap,当 leader 获取目标节点 Next-1 对应的记录的 term 值和需要发送的 Entries 出现异常时,就会生成 msgSnap 将快照数据发送到 follower 节点,follower 节点通过快照数据恢复状态,从而可以与 leader 进行正常的 entry 记录复制。
- MsgHeartbeat,leader 发送的心跳消息,主要作用是探测节点是否存活,follower 接收到 msgHeartbeat 会重置自身的选举计时器,防止 follower 发起新一轮的选举。同时尝试更新 follower 节点 raftLog 中已提交的位置。
- MsgHeartbeatResp,follower 处理心跳消息返回的消息类型。
- MsgUnreachable,如果 leader 发送 MsgSnap 消息出现异常,将会调用 ReportUnreachable () 发送该类型消息,将 follower 节点的状态改为 ProgressStateProbe。
- MsgSnapStatus,校验节点对应的状态是否为 ProgressStateSnapshot,如果之前发送的快照消息出现异常则将节点状态改为 ProgressStateProbe,之后单条发送消息。
- MsgCheckQuorum,leader 发送该消息类型检测是否保持半数以上连接。当 Leader 的心跳计时器超时,并且开启了 checkQuorum 模式 (raft 的 checkQuorum 字段为 true)。该 Leader 节点就会发送 MsgCheckQuorum 消息检测与集群中其他节点是否保持半数以上的连接,如果没有则变成 Follower 节点。
- MsgTransferLeader,发起 leader 节点转移的消息类型,本地消息。
- MsgTimeoutNow,如果 leader 节点转移超时,会发送该类型的消息,使 follower 的选举计时器立即过期,并发起新一轮的选举。
- MsgReadIndex,客户端发往集群的只读消息使用该类型。
- MsgReadIndexResp,只读消息类型的响应消息。
- MsgPreVote,当 Follower 的选举计时器超时时,会把当前状态切换成 StatePreCandidate(预选举),并向集群中其他节点发送 MsgPreVote。当集群中其他节点收到预选举消息时,会先进行一些检验,符合相关条件会投同意,否则会投拒绝票,然后发送给该候选节点,发送的消息类型为 MsgPreVoteResp。如果预选举阶段 (StatePreCandidate) 成功收到超过半数以上的同意票,那么该节点会认为选举成功,会发起新一轮的正式选举 (节点状态切换成 SateCandidate (候选人),发送的消息类型为 MsgVote)。是否有预选举阶段是根据初始化配置的参数,该字段保存在 raft 结构体的 preVote 字段中
- MsgPreVoteResp,其他节点响应预选举的投票消息。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。