热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

dataguard日志传输模式解析_SOFAJRaft日志复制pipeline实现剖析|SOFAJRaft实现原理

SOFAStack(ScalableOpenFinancialArchitectureStack)是蚂蚁金服自主研发的金融级分布式架构,包
a0a483c2753ec97e815a561151e5d1bb.png
SOFAStack(Scalable Open Financial Architecture Stack)
是蚂蚁金服自主研发的金融级分布式架构,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。

SOFAJRaft 是一个基于 Raft 一致性算法的生产级高性能 Java 实现,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。

本文为《剖析 | SOFAJRaft 实现原理》第六篇,本篇作者徐家锋,来自专伟信息,力鲲,来自蚂蚁金服。

《剖析 | SOFAJRaft 实现原理》系列由 SOFA 团队和源码爱好者们出品,项目代号:,文章尾部有参与方式,欢迎同样对源码热情的你加入。

SOFAJRaft :https://github.com/sofastack/sofa-jraft

本文的目的是要介绍 SOFAJRaft 在日志复制中所采用的 pipeline 机制,但是作者落笔时突然觉得这个题目有些唐突,我们不应该假设读者理所应当的对日志复制这个概念已经了然于胸,所以作为一篇解析,我觉得还是应该先介绍一下 SOFAJRaft 中的日志复制是要解决什么问题。

概念介绍

SOFAJRaft 是对 Raft 共识算法的 Java 实现。既然是共识算法,就不可避免的要对需要达成共识的内容在多个服务器节点之间进行传输,在 SOFAJRaft 中我们将这些内容封装成一个个日志块 (LogEntry),这种服务器节点间的日志传输行为在 SOFAJRaft 中也就有了专门的术语:日志复制

为了便于阅读理解,我们用一个象棋的故事来类比日志复制的流程和可能遇到的问题。

假设我们穿越到古代,要为一场即将举办的象棋比赛设计直播方案。当然所有电子通讯技术此时都已经不可用了,幸好象棋比赛是一种能用精简的文字描述赛况的项目,比如:“炮二平五”, “马8进7”, “车2退3”等,我们将这些描述性文字称为棋谱。这样只要我们在场外同样摆上棋盘 (可能很大,方便围观),通过棋谱就可以把棋手的对弈过程直播出来。

da200513e81ab7dcf51c5e7370e3f55d.png
图1 - 通过棋谱直播

所以我们的直播方案就是:赛场内两位棋手正常对弈,设一个专门的记录员来记录棋手走出的每一步,安排一个旗童飞奔于赛场内外,棋手每走一步,旗童就将其以棋谱的方式传递给场外,这样观众就能在场外准实时的观看对弈的过程,获得同观看直播相同的体验。

23050594200b98c7a3fd89ad7c47dd22.png
图2 - 一个简单的直播方案

这便是 SOFAJRaft 日志复制的人肉版,接下来我们完善一下这个“直播系统”,让它逐步对齐真实的日志复制。

改进1. 增加记录员的数量

假设我们的比赛获得了很高的关注度,我们需要在赛场外摆出更多的直播场地以供更多的观众观看。

2ee03096e97f151a78c4000c7adbb8c3.png
图3 - 更多的直播平台

这样我们就要安排更多的旗童来传递棋谱,场外的每一台直播都需要一个旗童来负责,这些旗童不停的在赛场内外奔跑传递棋谱信息。有的直播平台离赛场远一些,旗童要跑很久才行,相应的直播延迟就会大一些,而有些直播平台离得很近,对应的旗童就能很快的将对弈情况同步到直播。

随着直播场地的增加,负责记录棋局的记录员的压力就会增加,因为他要针对不同的旗童每次提供不同的棋谱内容,有的慢有的快。如果记录员一旦记混了或者眼花了,就会出现严重的直播事故(观众看到的不再是棋手真正的棋局)。

1bc9a283252973d262dda12c03f04691.png
图4 - 压力很大的记录员

为此我们要作出一些优化,为每个场外的直播平台安排一个专门的记录员,这样 “赛局-记录员-旗童-直播局” 就构成了单线模式,专人专职高效可靠。

36a00d2083970b38b775e8db42a9b479.png
图5 - “赛局-记录员-旗童-直播棋局”

改进2. 增加旗童每次传递的信息量

起初我们要求棋手每走一步,旗童就向外传递一次棋谱。可是随着比赛进行,其弊端也逐渐显现,一方面记录员记录了很多棋局信息没有传递出去,以至于不得不请求棋手停下来等待 (不可思议);另一方面,场外的观众对于这种“卡帧”的直播模式也很不满意。

所以我们做出改进,要求旗童每次多记几步棋,这样记录员不会积攒太多的待直播信息,观众也能一次看到好几步,而这对于聪明的旗童来说并不是什么难事,如此改进达到了共赢的局面。

b9560088018c55c2cd8cf5cb7d2e0ada.png
图6 - 旗童批量携带信息

改进3. 增加快照模式

棋局愈发精彩,应棋迷的强烈要求,我们临时增加了几个直播场地,这时棋手已经走了很多步了,按照我们的常规手段,负责新直播的记录员和旗童需要把过去的每一步都在直播棋盘上还原一遍(回放的过程),与此同时棋手还在不断下出新的内容。

从直觉上来说这也是一种很不聪明的方式,所以这时我们采用快照模式,不再要求旗童传递过去的每一步棋谱,而是把当前的棋局图直接描下来,旗童将图带出去后,按照图谱直接摆子。这样新直播平台就能快速追上棋局进度,让观众欣赏到赛场同步的棋局对弈了。

9529c3a780c22756d9fb3f41407674ed.png
图7 - 采用快照模式

改进4. 每一个直播平台用多个旗童传递信息

虽然我们之前已经在改进 2 中增加了旗童每次携带的信息量,但是在一些情况下(棋手下快棋、直播平台很远等),记录员依然无法将信息及时同步给场外。这时我们需要增加多个旗童,各旗童有次序的将信息携带到场外,这样记录员就可以更快速的把信息同步给场外直播平台。

19bfbdc825aa3d88687775b30a958a1f.png
图8 - 利用多个旗童传递信息,实现 pipeline 效果

现在这个人肉的直播平台在我们的逐步改进下已经具备了 SOFAJRaft 日志复制的下面几个主要特点:

特点1: 被复制的日志是有序且连续的

如果棋谱传递的顺序不一样,最后下出的棋局可能也是完全不同的。而 SOFAJRaft 在日志复制时,其日志传输的顺序也要保证严格的顺序,所有日志既不能乱序也不能有空洞 (也就是说不能被漏掉)。

111f5e7f83204a0acacff847d6303da8.png
图9 - 日志保持严格有序且连续

特点2: 复制日志是并发的

SOFAJRaft 中 Leader 节点会同时向多个 Follower 节点复制日志,在 Leader 中为每一个 Follower 分配一个 Replicator,专用来处理复制日志任务。在棋局中我们也针对每个直播平台安排一个记录员,用来将对弈棋谱同步给对应的直播平台。

241291ab510c3c10932692b937232b90.png
图10 - 并发复制日志

特点3: 复制日志是批量的

SOFAJRaft 中 Leader 节点会将日志成批的复制给 Follower,就像旗童会每次携带多步棋信息到场外。

2758c17d9f0b2d149ebc0dfc25d04734.png
图11 - 日志被批量复制

特点4: 日志复制中的快照

在改进 3 中,我们让新加入的直播平台直接复制当前的棋局,而不再回放过去的每一步棋谱,这就是 SOFAJRaft 中的快照 (Snapshot) 机制。用 Snapshot 能够让 Follower 快速跟上 Leader 的日志进度,不再回放很早以前的日志信息,即缓解了网络的吞吐量,又提升了日志同步的效率。

特点5: 复制日志的 pipeline 机制

在改进 4 中,我们让多个旗童参与信息传递,这样记录员和直播平台间就可以以“流式”的方式传递信息,这样既能保证信息传递有序也能保证信息传递持续。

在 SOFAJRaft 中我们也有类似的机制来保证日志复制流式的进行,这种机制就是 pipeline。Pipeline 使得 Leader 和 Follower 双方不再需要严格遵从 “Request - Response - Request” 的交互模式,Leader 可以在没有收到 Response 的情况下,持续的将复制日志的 AppendEntriesRequest 发送给 Follower。

在具体实现时,Leader 只需要针对每个 Follower 维护一个队列,记录下已经复制的日志,如果有日志复制失败的情况,就将其后的日志重发给 Follower。这样就能保证日志复制的可靠性,具体细节我们在源码解析中再谈。

182b58b2cbf87c089aa9e0741b1a243c.png
图12 - 日志复制的 pipeline 机制

源码解析

上面就是日志复制在原理层面的介绍,而在代码实现中主要是由 ReplicatorNodeImpl 来分别实现 Leader 和 Follower 的各自逻辑,主要的方法列于下方。在处理源码中有三点值得我们关注。

bb898dcd7f42bdcd8dac0cc7f02448f4.png
图13 - 相关的方法

关注1: Replicator 的 Probe 状态

b15e39bd471b6db5e4959717b51a0ef1.png
图14 - Replicator 的状态

Leader 节点在通过 Replicator 和 Follower 建立连接之后,要发送一个 Probe 类型的探针请求,目的是知道 Follower 已经拥有的的日志位置,以便于向 Follower 发送后续的日志。

f228c991a7defa8b0bb37968e1a94530.png
图15 - 发送探针来知道 follower 的 logindex

关注2: 用 Inflight 来辅助实现 pipeline

Inflight 是对批量发送出去的 logEntry 的一种抽象,他表示哪些 logEntry 已经被封装成日志复制 request 发送出去了。

4631d9f061dd8768d2de3b1f703fc9dd.png
图16 - Inflight 结构

Leader 维护一个 queue,每发出一批 logEntry 就向 queue 中 添加一个代表这一批 logEntry 的 Inflight,这样当它知道某一批 logEntry 复制失败之后,就可以依赖 queue 中的 Inflight 把该批次 logEntry 以及后续的所有日志重新复制给 follower。既保证日志复制能够完成,又保证了复制日志的顺序不变。

这部分从逻辑上来说比较清晰,但是代码层面需要考虑的东西比较多,所以我们在此处贴出源码,读者可以在源码中继续探索。

c614fdd1cf2a6a63d07eb3a31c3f0579.png
图17 - 复制日志的主要方法
ae54b49ca34e44f1a11c21aaca9ac021.png
图18 - 添加 Inflight 到队列中

当然在日志复制中其实还要考虑更加复杂的情况,比如一旦发生切换 leader 的情况,follower 该如何应对,这些问题希望大家能够进入源码来寻找答案。

关注3: 通信层采用单线程 & 单链接

在 pipeline 机制中,虽然我们在 SOFAJRaft 层面通过 Inflight 队列保证了日志是被有序的复制,对于乱序传输的 LogEntry 通过各种异常流程去排除掉,但是这些被排除掉的乱序日志最终还是要通过重传来保证最终成功,这就会影响日志复制的效率。

515727d18acb1e3c227c7b71b66a9209.png
图19 - 通信层不能保证有序

如上图所示,发送端的 Connection Pool 和 接收端的 Thread Pool 都会让原本“单行道”上有序传输的日志进入“多车道”,因而无法保证有序。所以在通信层面 SOFAJRaft 做了两部分优化去尽量保证 LogEntry 在传输中不会乱序。

1、在 Replicator 端,通过 uniqueKey 对日志传输所用的 Url 进行特殊标识 ,这样 SOFABolt (SOFAJRaft 底层所采用的通信框架) 就会为这种 Url 建立单一的连接,也就是发送端的 Connection Pool 中只有一条可用连接。

1c421409068bf4d5b6b1160a3e63431b.png
图20 - 通过 uniqueKey 定制 Url

2、在接收端不采用线程池派发任务,增加判断 dispatch_msg_list_in_default_executor 使得我们可以通过 io 线程直接将任务投递到 Processor 中。我们对 SOFABolt 做过一些功能增强,这里提供相关 PR #84 ,有兴趣的读者可以前往了解。

3820150f98fbdeceed6eafdec2cc508d.png
图21 - SOFABolt 利用 IO 线程派发 AppendEntriesRequest 到 Processor

这样日志复制的通信模型就变成了我们期望的“单行道”的模式。这种“单行道”能够很大程度上保证传输的日志是有序且连续的,从而提升了 pipeline 的效率。

5e889b11330b2e112b3f1cc3f1707715.png
图22 - 优化通信模型

总结

日志复制并不是一个复杂的概念,pipeline 机制也是一种符合直觉思维的优化方式,甚至在我们的日常生活中也能找到这些概念的实践。在 SOFAJRaft 中,日志复制的真正难点是如何在分布式环境下既考虑到各种细节和异常,又保证高性能。本文只是从概念上尝试介绍了日志复制,更多的细节还需读者进入代码去寻找答案。
SOFAJRaft 源码解析系列阅读

  • SOFAJRaft 选举机制剖析 | SOFAJRaft 实现原理
  • SOFAJRaft 线性一致读实现剖析 | SOFAJRaft 实现原理
  • SOFAJRaft 实现原理 - SOFAJRaft-RheaKV 是如何使用 Raft 的
  • SOFAJRaft 实现原理 - 生产级 Raft 算法库存储模块剖析
  • SOFAJRaft-RheaKV MULTI-RAFT-GROUP 实现分析 | SOFAJRaft 实现原理

公众号:金融级分布式架构(Antfin_SOFA)



推荐阅读
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 树莓派Linux基础(一):查看文件系统的命令行操作
    本文介绍了在树莓派上通过SSH服务使用命令行查看文件系统的操作,包括cd命令用于变更目录、pwd命令用于显示当前目录位置、ls命令用于显示文件和目录列表。详细讲解了这些命令的使用方法和注意事项。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
author-avatar
逍遥子2502897751
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有