TIKV 源码分析(一)raft-rs 组件

简介: Raft 是分布式领域中应用非常广泛的一种共识算法,相比于此类算法的鼻祖 Paxos,具有更简单、更容易理解和实现的特点。TiKV 依赖的周边库 raft-rs 是参照 ETCD 的 RAFT 库编写的 RUST 版本。本文不会详细介绍 RAFT 协议的原理或者实现,而是利用 raft-rs 的示例程序来讲解 raft-rs 如何使用。Public API 简述RawNode 结构体TIKV 的 

Raft 是分布式领域中应用非常广泛的一种共识算法,相比于此类算法的鼻祖 Paxos,具有更简单、更容易理解和实现的特点。TiKV 依赖的周边库 raft-rs 是参照 ETCD 的 RAFT 库编写的 RUST 版本。

本文不会详细介绍 RAFT 协议的原理或者实现,而是利用 raft-rs 的示例程序来讲解 raft-rs 如何使用。

Public API 简述

RawNode 结构体

TIKV 的 RAFT 对外接口是 RawNode 结构体:

pub struct RawNode<T: Storage> {
    /// The internal raft state.
    pub raft: Raft<T>,
    ...
}

这个结构体重要的接口有:

impl<T: Storage> RawNode<T> {
    pub fn propose(&mut self, context: Vec<u8>, data: Vec<u8>) -> Result<()>
    pub fn propose_conf_change(&mut self, context: Vec<u8>, cc: impl ConfChangeI) -> Result<()>
    pub fn step(&mut self, m: Message) -> Result<()>
    
    pub fn ready(&mut self) -> Ready
    
    pub fn advance(&mut self, rd: Ready) -> LightReady
    
    pub fn tick(&mut self) -> bool
    
}

Ready 结构体

我们知道,RAFT 中流转的 Log Entries 分为两种类型,一种是已经被大多数节点确认的 Log,叫做 committed entries,一种是暂时还未被大多数节点确认的 Log,就简单的叫做 Entries。两种 Log Entries 都可以通过 ready 函数接口从 RAFT 状态机中获取,这个就是 Ready 结构体:

pub struct Ready {
    ...

    // 发到 Raft 中,但尚未持久化的 Raft Log
    entries: Vec<Entry>,

    light: LightReady,
    
    ...
}

pub struct LightReady {
    // 已经持久化,并经过集群确认的 Raft Log。
    committed_entries: Vec<Entry>,
    
    // Raft 产生的消息,以便真正发给其他节点。
    messages: Vec<Message>,
}

RAFT 状态机流转

了解了 RAFT 的大概接口和 Ready 的大概作用,我们就可以了解使用 RAFT 的大概流程了

Leader 角度

  • 一阶段

在第一个阶段里,一份 Data 数据会被 RAFT 状态机转换为两份数据,一份数据转换为 Entries,然后落盘存储到 Disk,另一份数据转换为 Message,发送给其他 Follower 节点。

  • 应用接受到请求 Data 信息
  • 应用通过调用 RAFT 的 propose 接口将 Data 数据传递到 RAFT 状态机中去
  • 应用调用 Ready 函数等待 从 RAFT 中获取 Ready 结构体,从 Ready 结构体中拿出 Entries 和 Message,分别进行落盘和转化为 MsgAppend 信息传递给 Follower。
  • 应用还需要调用 advance 接口,来更新 RAFT 的内部状态,例如 Log index 信息,代表 Log Entries 已落盘。

  • 二阶段

  • Follower 收到 Message 进行处理后 (例如落盘) 会将 Entries 的确认信息 MsgAppend Response 发送回给 Leader,值得注意的是这个 Message 中含有 Follower 已接收的最新的 Log Entries Index。

  • 当 Leader 收到 Follower 节点的 Message 确认信息后,将会调用 step 函数将 Message 传递到 RAFT,RAFT 就会更新 Follower 的状态信息,尤其重要的是各个 Follower 的 Log Index 信息。

  • 应用调用 Ready 接口后,就会将大多数 Follower 确认的 Log Entries 放到 Ready 结构体,应用就会收到已确认的 Committed Entries,可以对其进行 Apply。

  • 之后依然还要调用 advance 接口,更新 RAFT 模块的状态,例如更新 Apply Index 信息,代表已提交。

  • 最后,Leader 在给 Follower 发送 HeartBeat Msg 的时候,会带着 Leader 的 Committed Index,以此来告知 Follower 对应的 Log Entries 已经被提交,Follower 可以进行对应的 Apply 流程了。

到此为止,Leader 和 Follower 已全部接受到最新的 Data 信息。 

Follower 角度

  • 第一阶段

  • Follower 收到 Leader 的 Message 信息后,应用会调用 step 函数将 MsgAppend 传递到 RAFT。这个 MsgAppend 中含有 Follower 需要落盘的 Log Entries 信息

  • 当用户调用 Ready 后,RAFT 就会将加工好的 Ready 结构体传递给应用,应用拿到 Log Entries 后进行落盘,然后将确认信息传递回 Leader。值得注意的是,RAFT 的 pipeline 要求 Leader 的落盘和 Message 的传递两个步骤是并行的,但是 Follower 必须落盘后才能调用 Transport Send,防止发送成功后,Follower 落盘失败。

  • 最后依然需要调用 advance 接口,更新 RAFT 状态。

  • 第二阶段

  • Follower 接受到 MsgHeartbeat 或者 MsgAppend 信息后,会从信息中获取 Leader 的 commit index

  • 应用调用 Ready 后,Follower 会根据 Leader 的 Commit Index,计算出 Committed Entries,从而对这些信息进行 Apply

至此,Leader 和大多数 Follower 都将 Log Entries 落盘,并对其数据进行 Apply

相关文章
|
3月前
|
存储 缓存 索引
etcd raft 处理流程图系列3-wal的存储和运行
etcd raft 处理流程图系列3-wal的存储和运行
44 1
|
4月前
|
索引
Etcd/Raft 原理问题之follower会进入StateReplicate状态时的问题如何解决
Etcd/Raft 原理问题之follower会进入StateReplicate状态时的问题如何解决
Etcd/Raft 原理问题之follower会进入StateReplicate状态时的问题如何解决
|
存储
zookeeper的leader选举原理和底层源码实现超级详解 2
zookeeper的leader选举原理和底层源码实现超级详解
78 1
|
存储
zookeeper的ZAB协议的原理以及底层源码实现超级详解 2
zookeeper的ZAB协议的原理以及底层源码实现超级详解
139 1
|
6月前
|
网络协议 中间件 数据库
Zookeeper学习系列【三】Zookeeper 集群架构、读写机制以及一致性原理(ZAB协议)
Zookeeper学习系列【三】Zookeeper 集群架构、读写机制以及一致性原理(ZAB协议)
251 0
|
网络协议 NoSQL Redis
【Redis源码】集群之分布式cluster原理(十四)
集群之分布式cluster原理(十四)
93 0
|
算法 Apache 文件存储
zookeeper的leader选举原理和底层源码实现超级详解 1
zookeeper的leader选举原理和底层源码实现超级详解
137 1
zookeeper的ZAB协议的原理以及底层源码实现超级详解 1
zookeeper的ZAB协议的原理以及底层源码实现超级详解
85 1
|
存储 索引
Raft实现日志-同步RPC设计
Raft实现日志-同步RPC设计
144 0
|
存储 JSON 算法
etcd-raft 模块如何实现分布式一致性?
etcd-raft 模块如何实现分布式一致性?
188 0