加入收藏 | 设为首页 | 会员中心 | 我要投稿 PHP编程网 - 黄冈站长网 (http://www.0713zz.com/)- 数据应用、建站、人体识别、智能机器人、语音技术!
当前位置: 首页 > 运营中心 > 建站资源 > 优化 > 正文

蚂蚁金服开源 SOFAJRaft:生产级 Java Raft 算法库

发布时间:2019-03-24 15:13:40 所属栏目:优化 来源:佚名
导读:副标题#e# 什么是 SOFAJRaft? SOFAJRaft 是一个基于Raft一致性算法的生产级高性能 Java 实现,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。 使用 SOFAJRaft 你可以专注于自己的业务领域,由 SOFAJRaft 负责处理所有与 Raft 相关的技术难题,并且 SO

SOFAJRaft 设计

蚂蚁金服开源 SOFAJRaft:生产级 Java Raft 算法库
  1. Node:Raft 分组中的一个节点,连接封装底层的所有服务,用户看到的主要服务接口,特别是 apply(task)用于向 raft group 组成的复制状态机集群提交新任务应用到业务状态机。
  2. 存储:上图靠下的部分均为存储相关。
  3. Log 存储,记录 Raft 用户提交任务的日志,将日志从 Leader 复制到其他节点上。
  4. LogStorage 是存储实现,默认实现基于 RocksDB 存储,你也可以很容易扩展自己的日志存储实现;
  5. LogManager 负责对底层存储的调用,对调用做缓存、批量提交、必要的检查和优化。
  6. Metadata 存储,元信息存储,记录 Raft 实现的内部状态,比如当前 term、投票给哪个节点等信息。
  7. Snapshot 存储,用于存放用户的状态机 snapshot 及元信息,可选:
  8. SnapshotStorage 用于 snapshot 存储实现;
  9. SnapshotExecutor 用于 snapshot 实际存储、远程安装、复制的管理。
  10. 状态机
  11. StateMachine:用户核心逻辑的实现,核心是 onApply(Iterator) 方法, 应用通过 Node#apply(task) 提交的日志到业务状态机;
  12. FSMCaller:封装对业务 StateMachine 的状态转换的调用以及日志的写入等,一个有限状态机的实现,做必要的检查、请求合并提交和并发处理等。
  13. 复制
  14. Replicator:用于 Leader 向 Followers 复制日志,也就是 Raft 中的 AppendEntries 调用,包括心跳存活检查等;
  15. ReplicatorGroup:用于单个 Raft group 管理所有的 replicator,必要的权限检查和派发。
  16. RPC:RPC 模块用于节点之间的网络通讯
  17. RPC Server:内置于 Node 内的 RPC 服务器,接收其他节点或者客户端发过来的请求,转交给对应服务处理;
  18. RPC Client:用于向其他节点发起请求,例如投票、复制日志、心跳等。
  19. KV Store:KV Store 是各种 Raft 实现的一个典型应用场景,SOFAJRaft 中包含了一个嵌入式的分布式 KV 存储实现(SOFAJRaft-RheaKV)。

SOFAJRaft Group

单个节点的 SOFAJRaft-node 是没什么实际意义的,下面是三副本的 SOFAJRaft 架构图:

蚂蚁金服开源 SOFAJRaft:生产级 Java Raft 算法库 SOFAJRaft Multi Group

单个 Raft group 是无法解决大流量的读写瓶颈的,SOFAJRaft 自然也要支持 multi-raft-group。

蚂蚁金服开源 SOFAJRaft:生产级 Java Raft 算法库 SOFAJRaft 实现细节解析之高效的线性一致读

什么是线性一致读? 所谓线性一致读,一个简单的例子就是在 t1 的时刻我们写入了一个值,那么在 t1 之后,我们一定能读到这个值,不可能读到 t1 之前的旧值 (想想 Java 中的 volatile 关键字,说白了线性一致读就是在分布式系统中实现 Java volatile 语义)。

蚂蚁金服开源 SOFAJRaft:生产级 Java Raft 算法库

如上图 Client A、B、C、D 均符合线性一致读,其中 D 看起来是 stale read,其实并不是,D 请求横跨了 3 个阶段,而读可能发生在任意时刻,所以读到 1 或 2 都行。

重要:接下来的讨论均基于一个大前提,就是业务状态机的实现必须是满足线性一致性的,简单说就是也要具有 Java volatile 的语义。

  1. 要实现线性一致读,首先我们简单直接一些,是否可以直接从当前 Leader 节点读?
  2. 仔细一想,这显然行不通,因为你无法确定这一刻当前的 "Leader" 真的是 Leader,比如在网络分区的情况下,它可能已经被推翻王朝却不自知。
  3. 最简单易懂的实现方式:同 “写” 请求一样,“读” 请求也走一遍 Raft 协议 (Raft Log)。
蚂蚁金服开源 SOFAJRaft:生产级 Java Raft 算法库

本图出自《Raft: A Consensus Algorithm for Replicated Logs》

这一定是可以的,但性能上显然不会太出色,走 Raft Log 不仅仅有日志落盘的开销,还有日志复制的网络开销,另外还有一堆的 Raft “读日志” 造成的磁盘占用开销,这在读比重很大的系统中通常是无法被接受的。

  1. ReadIndex Read
  2. 这是 Raft 论文中提到的一种优化方案,具体来说:
  3. Leader 将自己当前 Log 的 commitIndex 记录到一个 Local 变量 ReadIndex 里面;
  4. 接着向 Followers 发起一轮 heartbeat,如果半数以上节点返回了对应的 heartbeat response,那么 Leader 就能够确定现在自己仍然是 Leader (证明了自己是自己);
  5. Leader 等待自己的状态机执行,直到 applyIndex 超过了 ReadIndex,这样就能够安全的提供 Linearizable Read 了,也不必管读的时刻是否 Leader 已飘走 (思考:为什么等到 applyIndex 超过了 ReadIndex 就可以执行读请求?);
  6. Leader 执行 read 请求,将结果返回给 Client。
  7. 通过ReadIndex,也可以很容易在 Followers 节点上提供线性一致读:
  8. Follower 节点向 Leader 请求最新的 ReadIndex;
  9. Leader 执行上面前 3 步的过程(确定自己真的是 Leader),并返回 ReadIndex 给 Follower;
  10. Follower 等待自己的 applyIndex 超过了 ReadIndex;
  11. Follower 执行 read 请求,将结果返回给 Client。(SOFAJRaft 中可配置是否从 Follower 读取,默认不打开)
  12. ReadIndex小结:
  13. 相比较于走 Raft Log 的方式,ReadIndex 省去了磁盘的开销,能大幅度提升吞吐,结合 SOFAJRaft 的 batch + pipeline ack + 全异步机制,三副本的情况下 Leader 读的吞吐可以接近于 RPC 的吞吐上限;
  14. 延迟取决于多数派中最慢的一个 heartbeat response,理论上对于降低延时的效果不会非常显著。
  15. Lease Read
  16. Lease Read 与 ReadIndex 类似,但更进一步,不仅省去了 Log,还省去了网络交互。它可以大幅提升读的吞吐也能显著降低延时。
  17. 基本的思路是 Leader 取一个比 election timeout 小的租期(最好小一个数量级),在租约期内不会发生选举,这就确保了 Leader 不会变,所以可以跳过 ReadIndex 的第二步,也就降低了延时。可以看到 Lease Read 的正确性和时间是挂钩的,因此时间的实现至关重要,如果时钟漂移严重,这套机制就会有问题。
  18. 实现方式:
  19. 定时 heartbeat 获得多数派响应,确认 Leader 的有效性 (在 SOFAJRaft 中默认的 heartbeat 间隔是 election timeout 的十分之一);
  20. 在租约有效时间内,可以认为当前 Leader 是 Raft Group 内的唯一有效 Leader,,可忽略 ReadIndex 中的 heartbeat 确认步骤(2);
  21. Leader 等待自己的状态机执行,直到 applyIndex 超过了 ReadIndex,这样就能够安全的提供 Linearizable Read 了 。

(编辑:PHP编程网 - 黄冈站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读