jraft解析

1,529 阅读7分钟

一、选举机制

1、角色划分

Follower:完全被动,不能发送任何请求,只接受并响应来自leader 和 candidate 的 message,每个节点启动后的初始状态一定是follower;

Leader:处理所有来自客户端的请求,以及复制 log到所有follower;

Candidate:用来竞选一个新leader(candidate 由 follower 触发超时而来)

2、Leader选举流程

A、PreVote

一般来说,raft的选举仅且只有一轮投票选出集群leader,但会存在缺陷,就是网络分区后,少数派节点虽然都不会得到足够的票数成为分区Leader,但任选编号term却会不断增加,在网络分区恢复,少数派节点会因为term较大,而迫使多数派Leader下线,我们叫他做捣蛋鬼。为了避免捣蛋鬼谋权篡位,我们引入Pre-Vote,只有得到绝大多数选票,才有被提拔为候选人的资格
在PreVote算法中,Candidate首先要确认自己能赢得集群中大多数节点的投票,这样才会把自己的term增加,然后发起真正的投票,其他投票节点同意发起选举的条件是(同时满足下面两个条件):

  • 没有收到有效领导的心跳,至少有一次选举超时
  • Candidate的日志足够新(Term更大,或者Term相同raft index更大)

PreVote算法解决了网络分区节点在重新加入时,会中断集群的问题。在PreVote算法中,网络分区节点由于无法获得大部分节点的许可,因此无法增加其Term。然后当它重新加入集群时,它仍然无法递增其Term,因为其他服务器将一直收到来自Leader节点的定期心跳信息。一旦该服务器从领导者接收到心跳,它将返回到Follower状态,Term和Leader一致。

B、Vote

超时驱动:心跳间隔/Leader与followers 间通信超时触发选举的时间
随机的超时时间:降低选举碰撞导致选票被瓜分的概率
选举流程:

  • Follower –> Candidate

  • 赢得选举:Candidate –> Leader

  • 另一个节点赢得选举:Candidate –> Follower

  • 一段时间内没有任何节点器赢得选举:Candidate –> Candidate

C、实现时序

二、日志复制

JRaft 中的日志主要可以分为两大类:一类是系统内部运行产生的日志;另外一类是用户主动往 JRaft 集群提交指令所产生的日志

1、业务数据提交给Leader

A、NodeImpl

apply方法提供接口以让业务向 JRaft 集群提交操作指令,这些指令以 Task 的形式在集群中流转,并以日志的形式记录到 Leader 节点中,同时同步给集群中所有的 Follower 节点,并最终透传给所有成功完成日志复制的集群节点状态机。

将承载用户操作指令的 Task 封装成 LogEntry 对象,并以事件的形式投递给 Disruptor 队列进行异步处理,LogEntryAndClosureHandler 实现了 EventHandler 接口,用于消费 Disruptor 队列中的事件

B、LogManagerImpl

先将日志数据写入内存,并将日志数据以OTHER类型事件的形式提交给 Disruptor 队列,用于实现异步刷盘

C、BallotBox

投票箱ballotBox主要记录了日志提交的状态。每个节点提交日志成功后都会调用这个方法

D、FSMCallerImpl 传递消息透传给状态机

E、StateMachine 业务状态机

2、Leader提交数据给Follower

三、快照机制

该机制通过定期为本地的数据状态生成对应的快照文件,并删除对应的日志文件,从而降低对于磁盘空间的容量消耗。当一个新的节点加入集群时,不同于从 Leader 节点复制集群在此之前的所有日志文件,基于快照机制该节点只需要从 Leader 节点下载安装最新的快照文件即可。

1、Leader持久化快照

NodeImpl:leader节点

SnapshotExecutorImpl:快照执行器

FSMCallerImpl:调度状态机

StateMachine:业务系统定义,生成具体快照文件,通过SnapshotWriter记录快照文件名,及其元数据信息

JRaft 节点会在初始化期间(即执行Node的init方法)启动快照计时器 snapshotTimer,用于周期性生成快照(默认周期为 1 小时)

2、Follower持久化快照

Replicator: Leader节点通过复制器发起安装快照文件请求

NodeImpl:Follower节点接收安装请求

SnapshotExecutorImpl:注册新的快照文件下载任务,开始从 Leader 节点下载快照文件,并阻塞等待下载过程的完成

FSMCallerImpl:将快照数据透传给业务,并由业务决定如何在本地恢复快照所蕴含的状态数据

StateMachine:获取快照文件对应的元数据信息, 加载快照数据,并更新数据值

四、存储结构

1、日志数据存储

A、SegmentFile

记录日志数据信息,通过mmap方式保存

magic bytes first log index reserved [0x20 0x20] [... 8 bytes...] [8 bytes]

[record, record, ...]

Magic bytes data length data [0x57, 0x8A] [4 bytes] [bytes]

B、Checkpoint

检查点文件保存某一时刻SegmentFile文件的文件名和commitPos位置,通过ProtoBufFile保存

commitPos (4 bytes) + path(4 byte len + string bytes)

C、AbortFile

启动初始化创建 abort文件,正常退出删除abort文件,非正常退出可以通过abort文件存在判断出来

2、快照文件读写(__raft_snapshot_meta

A、存储结构

message LocalFileMeta {
optional bytes user_meta = 1;
optional FileSource source = 2;
optional string checksum = 3;
}

message SnapshotMeta {
required int64 last_included_index = 1;
required int64 last_included_term = 2;
repeated string peers = 3;
repeated string old_peers = 4;
repeated string learners = 5;
repeated string old_learners = 6;
}

message LocalSnapshotPbMeta {
message File {
required string name = 1;
optional LocalFileMeta meta = 2;
};
optional SnapshotMeta meta = 1;
repeated File files = 2;
}

B、类图

LocalSnapshotStorage:本地快照存储

LocalSnapshotWriter:读取元数据文件信息

SnapshotFileReader:写入元数据文件信息

3、元信息文件(raft_meta

A、存储结构

message StablePBMeta {
required int64 term = 1;
required string votedfor = 2;
};

B、核心类

LocalRaftMetaStorage基于 ProtoBuf 实现,RaftMetaStorage 默认实现 LocalRaftMetaStorage 是基于 ProtoBuf Message 本地存储 Raft 元数据,初始化元信息存储 StorageFactory 根据 Raft 元信息存储路径、 Raft 内部配置以及 Node 节点监控默认创建 LocalRaftMetaStorage 元信息存储。

jraft的ProtobufMsgFactory类使用protobuf的动态解析机制,来处理消息,所谓动态解析,就是消费者不根据proto文件编译生成的类来反序列化消息,而是通过proto文件生成的descriptor来构造动态消息类,然后反序列化(解析)消息。ProtoBuf提供了动态解析机制来解决这个问题,它要求提供二进制内容的基础上,再提供对应类的Descriptor对象,在解析时通过DynamicMessage类的成员方法来获得对象结果

五、基于jraft扩展kv存储

RheaKV 是一个轻量级的分布式的嵌入式的 KV 存储,基本API: get/put/delete 和跨分区 scan/batch put, distributed lock

1、部署角色

PD: 全局的中心总控节点,负责整个集群的调度,一个 PD server 可以管理多个集群,集群之间基于 clusterId 隔离;PD server 需要单独部署,当然,很多场景其实并不需要自管理,rheaKV 也支持不启用 PD,需要配置StoreEngineOptions(服务的必须配置)和PlacementDriverServerOptions实例

Store Client:kv存储的客户端,需要配置了RheaKVStoreOptions、regionRouteTableOptionsList(客户端必须配置)和PlacementDriverOptions实例。

StoreServer: kv存储的服务端,需要配置RheaKVStoreOptions、StoreEngineOptions(服务的必须配置)和PlacementDriverOptions实例

2、存储设计

目前支持 MemoryDB 和 RocksDB 两种实现:

  • MemoryDB 基于 ConcurrentSkipListMap 实现,有更好的性能,但是单机存储容量受内存限制

  • RocksDB 在存储容量上只受磁盘限制,适合更大数据量的场景

3、Client到StoreServer流程

读时序

写时序

4、PD Client(RemotePlacementDriverClient)到PD Server流程