目录网络
上一篇 源码分析 RocketMQ DLedger(多副本) 之日志复制(传播) ,可能有很多读者朋友们以为源码阅读较为枯燥,看的有点云里雾里,本篇将首先梳理一下 RocketMQ DLedger 多副本关于日志复制的三个核心流程图,而后再思考一下在异常状况下如何保证数据一致性。
@(本节目录)架构
上图是一个简易的日志复制的模型:图中客户端向 DLedger 集群发起一个写请求,集群中的 Leader 节点来处理写请求,首先数据先存入 Leader 节点,而后须要广播给它的全部从节点,从节点接收到 Leader 节点的数据推送对数据进行存储,而后向主节点汇报存储的结果,Leader 节点会对该日志的存储结果进行仲裁,若是超过集群数量的一半都成功存储了该数据,主节点则向客户端返回写入成功,不然向客户端写入写入失败。并发
接下来咱们来探讨日志复制的核心设计要点。app
为了方便对日志进行管理与辨别,raft 协议为一条一条的消息进行编号,每一条消息达到主节点时会生成一个全局惟一的递增号,这样能够根据日志序号来快速的判断数据在主从复制过程当中数据是否一致,在 DLedger 的实现中对应 DLedgerMemoryStore 中的 ledgerBeginIndex、ledgerEndIndex,分别表示当前节点最小的日志序号与最大的日志序号,下一条日志的序号为 ledgerEndIndex + 1 。分布式
与日志序号还与一个概念绑定的比较紧密,即当前的投票轮次。高并发
请思考以下问题,Leader 节点收到客户端的数据写入请求后,经过解析请求,提取数据部分,构建日志对象,并生成日志序号,用 seq 表示,而后存储到 Leader 节点中,而后将日志广播(推送)到其从节点,因为这个过程当中存在网络时延,若是此时客户端向主节点查询 seq 的日志,因为日志已经存储在 Leader 节点中了,若是直接返回给客户端显然是有问题的,那该如何来避免这种状况的发生呢?源码分析
为了解决上述问题,DLedger 的实现(应该也是 raft 协议的一部分)引入了已提交指针(committedIndex)。即当主节点收到客户端请求时,首先先将数据存储,但此时数据是未提交的,此过程能够称之为追加,此时客户端没法访问,只有当集群内超过半数的节点都将日志追加完成后,才会更新 committedIndex 指针,得以是数据可否客户端访问。.net
一条日志要能被提交的充分必要条件是日志获得了集群内超过半数节点成功追加,才能被认为已提交。线程
从上文得知,一个拥有3个节点的 DLedger 集群,只要主节点和其中一个从节点成功追加日志,则认为已提交,客户端便可经过主节点访问。因为部分数据存在延迟,在 DLedger 的实现中,读写请求都将由 Leader 节点来负责。那落后的从节点如何再次跟上集群的步骤呢?设计
要从新跟上主节点的日志记录,首先要知道的是如何判断从节点已丢失数据呢?
DLedger 的实现思路是,DLedger 会按照日志序号向从节点源源不断的转发日志,从节点接收后将这些待追加的数据放入一个待写队列中。关键中的关键:从节点并非从挂起队列中处理一个一个的追加请求,而是首先查阅从节点当前已追加的最大日志序号,用 ledgerEndIndex 表示,而后尝试追加 (ledgerEndIndex + 1)的日志,用该序号从代写队列中查找,若是该队列不为空,而且没有 (ledgerEndIndex + 1)的日志条目,说明从节点未接收到这条日志,发生了数据缺失。而后从节点在响应主节点 append 的请求时会告知数据不一致,而后主节点的日志转发线程其状态会变动为COMPARE,将向该从节点发送COMPARE命令,用来比较主从节点的数据差别,根据比较的差别从新从主节点同步数据或删除从节点上多余的数据,最终达到一致。于此同时,主节点也会对PUSH超时推送的消息发起重推,尽最大可能帮助从节点及时更新到主节点的数据。
更多问题,`欢迎你们留言与我一块儿探讨。若是以为文章对本身有些用处的话,麻烦帮忙点个赞,谢谢。
推荐阅读:RocketMQ 日志复制系列文章:
一、源码分析 RocketMQ DLedger 多副本存储实现
二、源码分析 RocketMQ DLedger(多副本) 之日志追加流程
三、源码分析 RocketMQ DLedger(多副本) 之日志复制(传播)
做者介绍:丁威,《RocketMQ技术内幕》做者,RocketMQ 社区布道师,公众号:中间件兴趣圈 维护者,目前已陆续发表源码分析Java集合、Java 并发包(JUC)、Netty、Mycat、Dubbo、RocketMQ、Mybatis等源码专栏。能够点击连接加入中间件知识星球 ,一块儿探讨高并发、分布式服务架构,交流源码。