本文整理自Apache RocketMQ社区PMC Member刘振东在2019年1月12日Apache RocketMQ开发者沙龙上海站的分享。通过本文,您将了解到:
1. 多副本技术的演进;
2. DLedger的介绍;
3. RocketMQ on DLedger;
4. 社区发展;
多副本技术的演进
1.2 Master/Slave
多副本最早的是Master/Slave架构,即简单地用Slave去同步Master的数据,RocketMQ最早也是这种实现。分为同步模式(Sync Mode)和异步模式(Async Mode),区别就是Master是否等数据同步到Slave之后再返回Client。这两种方式目前在RocketMQ社区广泛使用的版本中都有支持,原理图如下图1所示:
1.2 基于Zookeeper服务
随着Google三篇核心技术论文的发表(MapReduce、GFS和BigTable),分布式领域开启了快速发展。在Hadoop 生态中,诞生了一个基于 Paxos 算法选举Leader的分布式协调服务 Zookeeper。由于Zookeeper本身拥有高可用和高可靠的特性,随之诞生了很多基于Zookeeper的高可用高可靠的系统。具体做法如下图2 :
如图所示,假如系统里有3个节点,通过Zookeeper提供的一些接口,可以从3个节点中自动的选出一个master来。选出一个master后,另外两个没成功的就自然变成slave。选完之后,后续过程与传统实现方式中的复制一样。故基于Zookeeper的系统与基于Master/Slave系统最大的区别就是:选master的过程由手动选举变成依赖一个第三方的服务(比如Zookeeper或Etcd)的选举。基于Zookeeper的服务还存在一个变种,具体做法如下图3:
在第一种方式中,发起者(Client)和接收者(Server)都是在同一个进程中的。而在这种方式中Client是脱离于Server之外的,通过Zookeeper,从这三个Client中选出一个master来,选完master后把请求同时发送到3个Server里,这样也可以达到多副本的效果。
但是基于Zookeeper的服务也带来一个比较严重的问题:依赖加重,部署、运维和故障诊断成本都大大提高。因为运维Zookeeper是一件很复杂的事情。
1.3 基于Raft服务方式
Raft可以认为是Paxos的简化版。基于Raft的方式如下图4所示,与上述两种方式最大的区别是:leader 的选举是由自己完成的。比如一个系统有3个节点,这3个节点的leader是利用Raft的算法通过协调选举自己去完成的,选举完成之后,master到slave同步的过程仍然与传统方式类似。最大的好处就是去除了依赖,即本身变得很简单,可以自己完成自己的协调。
Raft Leader Election机制
Raft最大的好处就是可以实现自身leader选举。如果一个分布系统要自我协调,通常是采用“投票”的方式,在“投票”的时候,为了解决冲突问题,就采用了两个机制:Term和Quorum。
Term,即给每一次投票编号,以1、2、3这样的数字命名。
Quorum,即少数服从多数原则,每一次投票必须要得到多数派(N/2 + 1)的同意才认为是成功。
根据这两个机制,在一个Term中,一个节点只能投出一票,保证在一个Term中只有一个节点能选举成功。如果在一个Term中,没有节点获得大多数节点(N/2+1)的选票,选举失败,会重新发起选举。选举失败后,给每个节点设置合适的随机等待时间,会容易更快选举完成。选举成功之后就是跟之前比较相似的复制过程。
三种实现高可靠和高可用的方法优劣对比
Master/Slave,Based on Zookeeper/Etcd和Raft,这三种是目前分布式系统中,做到高可靠和高可用的基本的实现方法,各有优劣。
Master/Slave
优点:实现简单
缺点:不能自动控制节点切换,一旦出了问题,需要人为介入。
Based on Zookeeper/Etcd
优点:可以自动切换节点
缺点:部署、运维、诊断成本很高。
Raft
优点:可以自己协调,并且去除依赖。
缺点:实现Raft,在编码上比较困难。
DLedger的介绍
2.1 DLedger的定位
前文介绍了目前分布式系统中,做到高可靠和高可用的三种基本的实现方法。Raft算法现在解析的比较多,也比较成熟,代码实现难度也有所降低。DLedger 作为一个轻量级的Java Library,它的作用就是将Raft有关于算法方面的内容全部抽象掉,开发人员只需要关心业务即可。
如上图所示,DLedger 只做一件事情,就是CommitLog。Etcd虽然也实现了Raft协议,但它是自己封装的一个服务,对外提供的接口全是跟它自己的业务相关的。在这种对Raft的抽象中,可以简单理解为有一个StateMachine和CommitLog。CommitLog是具体的写入日志、操作记录,StateMachine是根据这些操作记录构建出来的系统的状态。在这样抽象之后,Etcd对外提供的是自己的StateMachine的一些服务。DLedger 的定位就是把上一层的StateMachine给去除,只留下CommitLog。这样的话,系统就只需要实现一件事:就是把操作日志变得高可用和高可靠。
这样做对消息系统还有非常特别的含义。消息系统里面如果还采用StateMachine + CommitLog的方式,会出现double IO的问题。因为消息本身可以理解为是一个操作记录,Dledger 会提供一些对原生CommitLog访问的API。通过这些API可以直接去访问CommitLog。这样的话,只需要写入一次就可以拿到写入的内容。DLedger 对外提供的是简单的API,如下图6所示。可以把它理解为一个可以无限写入操作记录的文件,可以不停append,每一个append的记录会加上一个编号。所以直接去访问DLedger 的话就是两个API:一个是append(data),另一个是get(index),即根据编号拿到相应的entry(一条记录)。
2.2 DLedger 的架构
DLedger的架构如下图7所示:
从前面介绍的多副本技术的历史可以知道,我们要做的主要有两件事:选举和复制,对应到上面的架构图中,也就是两个核心类:DLedgerLeaderElector和DLedgerStore,选举和文件存储。选出leader后,再由leader去接收数据的写入,同时同步到其他的follower,这样就完成了整个Raft的写入过程。
2.3 DLedger 的代码
因为DLedger 只有CommitLog,没有StateMachine,所以代码很精简,只有4000多行,总体代码测试覆盖率大概是70%。通过如下几行命令便可以快速地体验:
DLedger源代码地址:https://github.com/openmessaging/openmessaging-storage-DLedger
RocketMQ on DLedger
3.1DLedger 在RocketMQ上的应用
DLedger虽然只需要写CommitLog,但是基于CommitLog是可以做很多事情的。RocketMQ原来的架构里是有CommitLog的,现在用DLedger 去替代原来的CommitLog。由于DLedger 提供了一些可以直接读取CommitLog的API,于是就可以很方便地根据CommitLog去构建ConsumerQueue或者其他的模块。这就是DLedger 在RocketMQ上最直接的应用。
3.2DLedger 与RocketMQ对接时格式上的区别
加了DLedger 之后,其实就是在原来的消息上面加了一个头。这个头就是DLedger 的头,本质上是一些简单的属性,例如size等。如下图10所示。
在使用的时候会有格式上的差异,所以社区在升级的时候做了一个平滑升级的考虑,如图11所示。要解决的问题是:如何将原来已有的CommitLog和现在基于DLedger 的CommitLog混合?现在已经支持自动混合,但是唯一的一个要求是旧版的CommitLog需要自己去保持它的一致性。因为DLedger 的复制是从新写入的记录开始。假设旧的CommitLog已经是一致的情况下,然后直接启动DLedger ,是可以在旧的CommitLog基础上继续append,同时保证新写消息的一致性,高可靠和高可用。这是DLedger 直接应用到RocketMQ上的时候一个格式上的区别。
对于用户来说,这样做最直接的好处是:若使用Master/Slave架构模式,一旦一个broker挂了,则需要手动控制。但是使用DLedger 之后不需要这么做。因为DLedger 可以通过自己选举,然后把选举结果直接传给RocketMQ的broker,这样通过nameserver拿到路由的时候就可以自动找到leader节点去访问消息,达到自动切换的目的。如下图12所示:
3.3DLedger 多中心
上面只是最基本的应用,更直接的应用如图13所示:(容灾切换)
这里举一个真实的金融应用场景:杭州、深圳和上海分别代表系统中三个节点,进行消息传输。要求:数据一条都不能丢,满足实时高可用并且考虑城市之间的容灾。
Raft的论文中没有提及如何优先选择某个节点作为leader,但是我们实现的时候可以自己优化。假设杭州节点服务更好,我们可以指定优先选择杭州节点为leader。如果杭州节点宕机,可以再把leader节点切换到上海,如果上海节点也挂掉,虽然集群不可用了,但是大部分数据还在深圳节点有灾备。深圳主要起复制和灾备的作用,一般情况下不会被选为leader。
3.4 DLedger 使用方式
社区4.4.0刚刚发布,RocketMQ on DLedger计划在4.5.0发布,目前可以在分支上获取代码:
https://github.com/apache/rocketmq/blob/store_with_DLedger/docs/cn/DLedger/
下载代码之后,可以通过运行社区提供的简单脚本fast-try.sh来体验一下。
社区发展
4.1 功能点展望
前面主要讲了两个东西:RocketMQ和DLedger。DLedger主要是作为openmessaging社区的一个项目在孵化,openmessaging针对消息的存储也抽象出了一套API,DLedger是其一个标准的实现。
目前Github上的DLedger只是实现了Raft一些基础功能。后续有很多可以扩展的功能点,比如:
Leader节点优先选择
手动配置leader
自动降级到master/slave架构
4.2 openmessaging-hakv
再看一下跟DLedger定位相似的java library:openmessaging-hakv。openmessaging-hakv结构图如图15所示。代码详见:
https://github.com/openmessaging/openmessaging-hakv
目前来看,我们没有一个嵌入式且高可用的解决方案。RocksDB可以直接用,但是它本身不支持高可用。有了DLedger之后,我们可以把操作的记录直接写入DLedger,但是基于这些记录恢复过程我们可以自己选择。假如数据量少,对高可用要求高,比如元数据,我们可以直接存在内存的hashmap中,根据DLedger的写入记录来构建自己的hashmap,从而达到数据的一致性和容灾功能。