长期以来,在多中心、多中心消息发送的场景下,如何保证数据的完整性和一致性一直是一个技术难点。在RocketMQ4.5之前,RocketMQ只有一种部署方式,Master/Slave。一组broker中有一个Master,以及零个或多个Slave。Slaves通过同步或异步复制同步Master数据。Master/Slave部署模式提供了一定的高可用性。但是,这种部署模型有一些缺点。例如,在故障转移方面,如果主节点挂掉,需要手动重启或切换,而从节点不能自动转换为主节点。那么什么样的多副本架构可以解决这个问题呢?首先,让我们看一下多副本技术的演变。多副本技术的演变Master/Slave多副本是最早的Master/Slave架构,也就是简单的使用Slave来同步Master的数据,RocketMQ也是最早的实现。分为同步模式(SyncMode)和异步模式(AsyncMode),区别在于Master是否要等数据同步到Slave后才返回给Client。目前RocketMQ社区广泛使用的版本都支持这两种方式,大家也可以阅读我之前分享的文章。基于Zookeeper的服务随着分布式领域的发展而迅速发展。在Hadoop生态中,诞生了一个基于Paxos算法选举Leader的分布式协调服务ZooKeeper。由于ZooKeeper本身具有高可用、高可靠的特性,因此诞生了很多基于ZooKeeper的高可用、可靠系统。具体方法如下图所示:基于Zookeeper/Etcd如图所示,如果系统中有3个节点,通过ZooKeeper提供的一些接口,可以自动从3个节点中选出一个Master。选出一个Master后,另外两个选不中的自然会成为Slave。选择后,后续过程与传统实现中的复制相同。因此,基于ZooKeeper的系统与基于Master/Slave的系统最大的区别在于,选择Master的过程从手动选举变成了依赖第三方服务(如ZooKeeper或Etcd)的选举。但是基于ZooKeeper的服务也带来了一个更严重的问题:依赖性增加。因为运维ZooKeeper是一件非常复杂的事情。由于基于Raft服务方式的ZooKeeper的复杂性,可用以下Raft方式。Raft可以认为是Paxos的简化版。基于Raft的方法如下图4所示。与以上两种方式最大的区别在于leader的选举是自己完成的。例如,一个系统有3个节点。这3个节点的leader使用Raft算法,通过协调自行完成选举。选举完成后,从Master到Slave的同步过程还是和传统方式类似。最大的好处就是去掉了依赖,也就是变得很简单,自己完成协调就可以实现高可靠和高可用。与Master/Slave、BasedZooKeeper/Etcd和Raft相比,这三种是目前分布式系统中,实现高可靠和高可用的基本实现方式,各有优缺点。Master/Slave优点:易于实现缺点:不能自动控制节点切换,一旦出现问题,需要人工干预。基于Zookeeper/Etcd优点:可以自动切换节点缺点:运维成本高,因为ZooKeeper本身运维难度大。Raft的优点:可以自我协调,去除依赖。缺点:Raft在编码上实现起来比较困难。多副本架构首先要解决自动故障转移的问题,本质上就是自动选主的问题。这个问题的解决方案基本上可以分为两种:使用第三方协调服务集群来完成选主,比如zookeeper或者etcd。该方案会引入重量级的外部组件,增加部署、运维和故障诊断的成本。比如维护RocketMQ集群也需要维护zookeeper集群,zookeeper集群故障会影响到RocketMQ集群。raft协议用于完成自动选主。与前者相比,raft协议的优势在于不需要引入外部组件。自动选主逻辑集成到每个节点的进程中,通过节点间通信完成选主。目前很多中间件使用raft协议或者raft协议的变种,比如mongodb。还有一个新版本的kafka,它放弃了zookeeper,将元数据存储在Kafka本身,而不是ZooKeeper等外部系统。新版Kafka的Quorumcontroller使用新的KRaft协议来确保元数据在quorum中被准确复制。该协议在很多方面类似于ZooKeeper的ZAB协议和Raft。RocketMQ也选择使用raft协议来解决这个问题。关于Raft,我们知道在分布式领域,我们必须时刻面对的挑战之一就是:数据的一致性。帕克索斯。如今,它已被业界公认为解决此类问题的最有效解决方案。Paxos虽然在理论界得到了高度认可,但也给工程界带来了难题。由于算法本身比较晦涩抽象,缺少很多实现细节。这让很多工程师很头疼。Raft的提出是为了解决Paxos难以理解和实现的问题。Raft算法的工作流程主要包括五个部分:Leader选举:当集群初始化完成或老Leader异常时,选举出新的Leader。日志复制:当写入新日志时,领导者可以将其复制到集群中的大多数节点。集群成员变化:当集群需要扩容或缩容时,集群中的每个节点都可以准确感知哪些节点是新增或移除的。日志压缩:当写入的日志文件越来越大时,节点重启时重放日志的时间会无限延长,新节点加入集群时日志文件也会无限放大。日志文件需要定期重组和压缩。读写一致性:客户端是集群的外部组件。当一个客户端写入新数据时,可以保证所有后续客户端都能读取到最新的值。数据一致性的几层语义:数据写入的顺序必须一致。否则,可能会出现很多意想不到的情况,例如:旧值覆盖新值。先删后加变成先加后删,数据就消失了。我承认写入成功的数据。如果数据被集群成功写入,集群中的所有节点都应该承认并接受结果,而不会有一些节点不知道。成功的数据写入保证持久化。如果集群提示数据写入成功,说明数据还没有写入磁盘。如果此时崩溃,数据就会丢失。Raft可以很好地支持保序、共识和持久化。这证明:在leader永不宕机的前提下,Raft可以保证集群的数据一致性。当leader不正常运行时,选出的新leader至少有所有提交的日志,这样才能保证数据的一致性。因为Raft规定所有写操作必须由Leader控制,所以在leader选举期间,客户端的写操作会被通知失败或者反复重试。这里实际上一定程度上牺牲了集群的可用性来保证一致性。但是,正如CAP定理告诉我们的,分布式系统不能同时保证一致性C和可用性A。但是Raft集群选择了C和P,从而在一定程度上失去了A。因此,Raft算法可以保证集群的数据一致性。什么是DLedgerDledger的定位DLedger是一个基于raft协议的commitlog仓库。作为一个轻量级的JavaLibrary,Dledger的作用是将Raft的所有算法内容抽象出来。开发者只需要关心业务也是RocketMQ全新的高可用多副本架构的关键。如上图所示,Dledger只做了一件事,就是CommitLog。Etcd虽然也实现了Raft协议,但是它是一个自我封装的服务,它提供的接口都是和自身业务相关的。在Raft的这个抽象中,可以简单理解为有一个StateMachine和CommitLog。CommitLog是具体的写日志和操作记录,StateMachine是根据这些操作记录构建的系统状态。经过这样的抽象,Etcd对外提供了自己StateMachine的一些服务。Dledger的定位是去掉上层的StateMachine,只留下CommitLog。在这种情况下,系统只需要实现一件事:使操作日志高可用和可靠。Dledger的架构可以从前面介绍的多副本技术的演变过程中得知。我们需要做的主要有两件事:选举和复制,对应上面的架构图,也就是两个核心类:DLedgerLeaderElector和DLedgerStore,选举和文件存储。leader选好后,leader会收到数据的写入,同时同步给其他follower,这样就完成了整个Raft写入过程。DLedger优化后的Raft协议复制过程可以分为四个步骤。首先,给领导发个信息。除了本地存储,leader还会将消息复制给follower,然后等待follower的确认。如果得到大多数节点的确认,则可以提交消息,并向客户端返回一个成功的确认。DLedger对复制过程有以下优化:1.对于复制过程,DLedger使用异步线程模型来提高吞吐量和性能。2.在DLedger中,leader独立并发地向所有follower发送日志。领导者给每个跟随者分配一个线程来复制日志。这是一个独立的并发复制过程。3.在独立并发复制过程中,DLedger设计并实现了并行日志复制的方案。它不再需要等待上一个日志被复制,然后再复制下一个日志。它只需要在follower中维护一个按日志索引排序的请求列表,followerThreads按照索引顺序串行处理这些复制请求。至于并行复制后可能出现的数据丢失问题,可以通过重传少量数据来解决。通过以上3点,优化这个复制过程。在可靠性方面,DLedger也对网络分区进行了优化,同时也对DLedger进行了一次可靠性测试:DLedger对网络分区进行了优化。如果出现上图所示的网络分区,则表示n2与集群中的其他节点隔离。根据raftpaper的实现,n2会一直要求选票,但得不到多数选票,term不断增加。一旦网络恢复,n2将中断正在正常复制的n1和n3进行重新选举。为了解决这种情况,DLedger的实现改进了raft协议,请求投票过程分为多个阶段,其中有两个重要的阶段:WAIT_TO_REVOTE和WAIT_TO_VOTE_NEXT。WAIT_TO_REVOTE是初始状态。在这种状态下,请求投票时不会增加任期。WAIT_TO_VOTE_NEXT将在下一轮请求投票开始前增加任期。对于图中n2的情况,当有效票数没有达到多数时。可以设置节点状态为WAIT_TO_REVOTE,任期不会增加。这样,Dledger对网络分区的容忍度就提高了。官方DLedger可靠性测试不仅测试了对称网络分区故障,还测试了Dledger在其他故障下的性能,包括随机杀节点,随机挂起部分节点进程模拟慢节点状态,复杂问题如桥和分区多数环非对称网络分区。在这些故障下,DLedger保证了一致性,这验证了DLedger具有良好的可靠性。RocketMQ4.5发布后,可以使用RocketMQonDLedger部署RocketMQ上的Dledger应用程序。DLedgercommitlog替换原来的commitlog,让commitlog具备选举复制的能力,然后通过角色透传,将raft角色透传给外部broker角色,leader对应原来的master,follower和candidate对应于原始奴隶。因此,RocketMQ的broker具有自动故障转移的能力。在一组broker中,Master挂掉后,依靠DLedger的自动选出leader的能力,leader会重新选举出来,然后通过角色透传成为新的Master。DLedger构建高可用的嵌入式KV存储DLedger也可以构建高可用的嵌入式KV存储。我们没有嵌入式且高度可用的解决方案。RocksDB可以直接使用,但是本身不支持高可用。(RocksDB是Facebook的开源单机数据库。)通过DLedger,我们将对一些数据的操作记录到DLedger中,然后根据数据量或实际需要将其还原到hashmap或rocksdb,从而构建一个一致且高度可用的数据库。KV存储系统应用于元数据管理等场景。参考:https://developer.aliyun.com/article/713017https://blog.csdn.net/csdn_lhs/article/details/108029978
