一致性算法 - Raft协议实践(SOFAJRaft剖析)

-     SOFAJRaft 概述    -算法

我们对Raft协议已经进行了原理的解析,接下去我们从经过SOFAJRaft 框架的核心流程剖析加深对Raft协议的理解。SOFAJRaft 是一个纯 Java 的 Raft 算法实现库, 基于百度 braft 实现而来, 使用 Java 重写了全部功能, 支持:缓存

  • 领导人选举和基于优先级的半肯定性领导人选举。微信

  • 日志复制和恢复。网络

  • 快照和日志压缩。架构

  • 只读成员(learner)。并发

  • 集群成员管理,添加节点,删除节点,替换节点等。app

  • 彻底并发复制。负载均衡

  • 容错能力。框架

  • 非对称网络分区容忍性。分布式

  • 当法定人数同伴都死亡的解决方法。

  • 管道复制

  • 线性一致读,ReadIndex/LeaseRead。

额外扩展了一些功能:

  • 对称网络分区容忍性

  • 重启后的转移领袖、负载均衡场景实现

  • 更丰富的指标统计展现

  • 经过Jepsen一致性验证测试

  • 包含嵌入式分布式KV存储实现

总体项目以下:

-     领袖选举    -

SOFAJRaft 的选举主要经过判单两个属性:LogIndex 和 Term;Term 即任期,LogIndex即提交到 raft group 中的任务都将序列化为一条日志存储下来,每条日志一个编号,在整个 raft group 内单调递增并复制到每一个 raft 节点。能够理解为事务id。投票处理的逻辑主要在 com.alipay.sofa.jraft.core.NodeImpl中,主要有四个函数:
  • 处理处理预投票请求

    Message handlePreVoteRequest(request)

  • 预投票

    void preVote()

  • 处理投票请求

    Message handleRequestVoteRequest(request)

  • 投票

    electSelf()

总体流程以下:

  • Candidate(候选人) 被 Election timeout触发

  • Candidate 开始尝试发起 pre-vote 预投票

  • Follower(追随者) 判断是否定可该 pre-vote request

  • Candidate 根据 pre-vote response 来决定是否发起 RequestVoteRequest

  • Follower 判断是否定可该 RequestVoteRequest

  • Candidate 根据 response 来判断本身是否当选

使用预投票能够防止网络抖动等特殊缘由引发的瞬时失联节点无端捣乱:候选者在发起投票以前,先发起预投票,若是没有获得半数以上节点的反馈,则候选者就会识趣的放弃参选,也就不会抬升全局的 Term。

投票源码:

预投票源码:

-     存储机制    -

SOFAJRaft 存储模块分为:

  • Log 存储记录 Raft 配置变动和用户提交任务日志,把日志从 Leader 复制到其余节点上面;

    • LogStorage 是日志存储实现,默认实现基于 RocksDB 存储,经过 LogStorage 接口扩展自定义日志存储实现;核心接口包括:

      • 返回日志里的首/末个日志索引;

      • 按照日志索引获取 Log Entry 及其任期;

      • 把单个/批量 Log Entry 添加到日志存储;

      • 从 Log 存储头部/末尾删除日志;

      • 删除全部现有日志,重置下任日志索引。

    • LogManager 负责调用底层日志存储 LogStorage,针对日志存储调用进行缓存、批量提交、必要的检查和优化。

      • checkAndResolveConflict(entries, done)

        1. 检查Node节点,解决日志冲突。

        2. 配置管理器:缓存配置变动

        3. LogsInMemory缓存日志Entries

      • offerEvent(done, type)

        Disruptor队列发布other类型事件

      • appendToStorage(toAppend)

        回调事件处理器StableClosureEventHandler存储日志

  • Meta 存储即元信息存储记录 Raft 实现的内部状态,好比当前 term,、投票给哪一个节点等信息

    • RaftMetaStorage 元信息存储实现,定义 Raft 元数据的 Metadata 存储模块核心 API 接口包括:

      • 设置/获取 Raft 元数据的当前任期 Term;

      • 分配/查询 Raft 元信息的 PeerId 节点投票。

  • Snapshot 存储用于存放用户的状态机 Snapshot 及元信息,用于Node重启重建整个状态机实例。

    • SnapshotStorage 用于 snapshot 存储实现,定义 Raft 状态机的 Snapshot 存储模块核心接口包括:

      • 设置 filterBeforeCopyRemote ,为 true 表示复制到远程以前过滤数据;

      • 建立快照编写器;

      • 打开快照阅读器;

      • 从远程 Uri 复制数据;

      • 启动从远程 Uri 复制数据的复制任务;

      • 配置 SnapshotThrottle,SnapshotThrottle 用于重盘读/写场景限流的,好比磁盘读写、网络带宽。

    • SnapshotExecutor 用于 snapshot 实际存储、远程安装、复制的管理。

      • 状态机快照 doSnapshot(done)

      • 安装快照 installSnapshot(request, response, done)。

LogManager 调用日志存储 LogStorage 实现逻辑:

SnapshotExecutor 状态机快照和远程安装镜像实现逻辑:

-     一致性状态机    -

经过存储的设计,在引入状态机机制,就能够完成一致性状态机。SOFAJRaft状态机组成有:

  • StateMachine:业务逻辑实现的主要接口,状态机运行在每一个 raft 节点上,提交的 task 若是成功,最终都会复制应用到每一个节点的状态机上。,核心是 onApply(Iterator) 方法,应用经过 Node#apply(task) 提交的日志到业务状态机。

  • FSMCaller:封装对业务 StateMachine 的状态转换的调用以及日志的写入等,一个有限状态机的实现,作必要的检查、请求合并提交和并发处理等。

SOFAJRaft Node节点利用日志复制完成数据同步,主要组成有:

  • Replicator:用于 leader 向 follower 复制日志,也就是 raft 中的 appendEntries 调用,包括心跳存活检查等。

  • ReplicatorGroup: 用于单个 RAFT Group 管理全部的 replicator,必要的权限检查和派发。

-     总结    -

      本文经过简单介绍了下SOFAJRaft的选举实现、存储机制、状态机和日志复制四个方面。基本上完成了Raft实现的核心实现。但SOFAJRaft还有更多核心及优化,由于篇幅缘由没有进入细细剖析。若是我们自实现Raft协议,基本上也是实现这几个主流程便可完成简版Raft了。关于Raft协议暂时先告一段路,接下去准备开写ZAB协议。

-     做者介绍    -

林淮川

毕业于西安交通大学;奈学教育《百万架构师训练营》讲师、企业级源码内源负责人,前大树金融高级架构师、技术委员会开创者、技术总监;前天阳宏业交易事业部技术主管;多年互联网金融行业(ToB)经验。


 

本文分享自微信公众号 - 川聊架构(gh_44ec4115d261)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索