一直以来,在多地多中心的消息发送场景下,如何保障数据的完整性和一致性是一个技术难点。本文将和您一起探讨 Dledger 技术,并分享 RocketMQ 的实践。
1. 多副本技术的演进
1.1 Master/Slave
多副本最早的是 Master/Slave 架构,即简单地用 Slave 去同步 Master 的数据,RocketMQ 最早也是这种实现。分为同步模式(Sync Mode)和异步模式(Async Mode),区别就是 Master 是否等数据同步到 Slave 之后再返回 Client。这两种方式目前在 RocketMQ 社区广泛使用的版本中都有支持,原理图如下图 1 所示。
图 1 Master-Slave
1.2 基于 Zookeeper 服务
随着 Google 三篇核心技术论文的发表(MapReduce、GFS 和 BigTable),分布式领域开启了快速发展。在 Hadoop 生态中,诞生了一个基于 Paxos 算法选举 Leader 的分布式协调服务 ZooKeeper。[z1] 由于 ZooKeeper 本身拥有高可用和高可靠的特性,随之诞生了很多基于 ZooKeepe r 的高可用高可靠的系统。具体做法如下图 2 所示:
图 2 Based on Zookeeper/Etcd
如图所示,假如系统里有 3 个节点,通过 ZooKeeper 提供的一些接口,可以从 3 个节点中自动的选出一个 Master 来。选出一个 Master 后,另外两个没成功的就自然变成 Slave。选完之后,后续过程与传统实现方式中的复制一样。故基于 ZooKeeper 的系统与基于 Master/Slave 系统最大的区别就是:选 Master 的过程由手动选举变成依赖一个第三方的服务(比如 ZooKeeper 或 Etcd)的选举。
基于 ZooKeeper 的服务还存在一个变种,具体做法如下图 3:
图 3 Based on ZooKeeper/Etcd
在第一种方式中,发起者(Client)和接收者(Server)都是在同一个进程中的。而在这种方式中 Client 是脱离于 Server 之外的,通过 Zookeeper,从这三个 Client 中选出一个 Master 来,选完 Master 后把请求同时发送到 3 个 Server 里,这样也可以达到多副本的效果。
但是基于 ZooKeeper 的服务也带来一个比较严重的问题:依赖加重。因为运维 ZooKeeper 是一件很复杂的事情。
1.3 基于 Raft 服务方式
因为 ZooKeeper 的复杂性,又有了以下 Raft 的方式。Raft 可以认为是 Paxos 的简化版。基于 Raft 的方式如下图 4 所示,与上述两种方式最大的区别是:leader 的选举是由自己完成的。比如一个系统有 3 个节点,这 3 个节点的 leader 是利用 Raft 的算法通过协调选举自己去完成的,选举完成之后,Master 到 Slave 同步的过程仍然与传统方式类似。最大的好处就是去除了依赖,即本身变得很简单,可以自己完成自己的协调。
图 4 Raft
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
优点:可以自动切换节点
缺点:运维成本很高,因为 ZooKeeper 本身就很难运维。
Raft
优点:可以自己协调,并且去除依赖。
缺点:实现 Raft,在编码上比较困难。
2. 什么是 DLedger
2.1 Dledger 的定位
前文介绍了目前分布式系统中,做到高可靠和高可用的三种基本的实现方法。Raft 算法现在解析的比较多,也比较成熟,代码实现难度也有所降低。Dledger 作为一个轻量级的 Java Library,它的作用就是将 Raft 有关于算法方面的内容全部抽象掉,开发人员只需要关心业务即可。
图 5 Dledger Proposition
如上图所示,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,另一个是 get,即根据编号拿到相应的 entry(一条记录)。
图 6 Dledger API
2.2 Dledger 的架构
Dledger 的架构如下图 7 所示:
图 7 Dledger Architecture
从前面介绍的多副本技术的历史可以知道,我们要做的主要有两件事:选举和复制,对应到上面的架构图中,也就是两个核心类:DLedgerLeaderElector 和 DLedgerStore,选举和文件存储。选出 leader 后,再由 leader 去接收数据的写入,同时同步到其他的 follower,这样就完成了整个 Raft 的写入过程。
2.3 Dledger 的代码
因为 Dledger 只有 CommitLog,没有 StateMachine,所以代码很精简,只有 4000 多行,总体代码测试覆盖率大概是 70%。通过如下几行命令便可以快速地体验:
图 8 Dledge Quick Start
Dledger 源代码地址:https://github.com/openmessaging/openmessaging-storage-dledger
3. RocketMQ on DLedger
3.1 Dledger 在 RocketMQ 上的应用
图 9 RocketMQ on Dledger Architecture
Dledger 虽然只需要写 CommitLog,但是基于 CommitLog 是可以做很多事情的。RocketMQ 原来的架构里是有 CommitLog 的,现在用 Dledger 去替代原来的 CommitLog。由于 Dledger 提供了一些可以直接读取 CommitLog 的 API,于是就可以很方便地根据 CommitLog 去构建 ConsumerQueue 或者其他的模块。这就是 Dledger 在 RocketMQ 上最直接的应用。
3.2 Dledger 与 RocketMQ 对接时格式上的区别:
加了 Dledger 之后,其实就是在原来的消息上面加了一个头。这个头就是 Dledger 的头,本质上是一些简单的属性,例如 size 等。如下图 10 所示。
图 10 RocketMQ on Dledger Format
在使用的时候会有格式上的差异,所以社区在升级的时候做了一个平滑升级的考虑,如图 11 所示。要解决的问题是:如何将原来已有的 CommitLog 和现在基于 Dledger 的 CommitLog 混合?现在已经支持自动混合,但是唯一的一个要求是旧版的 CommitLog 需要自己去保持它的一致性。因为 Dledger 的复制是从新写入的记录开始。假设旧的 CommitLog 已经是一致的情况下,然后直接启动 Dledger ,是可以在旧的 CommitLog 基础上继续 append,同时保证新写消息的一致性,高可靠和高可用。这是 Dledger 直接应用到 RocketMQ 上的时候一个格式上的区别。
图 11 RocketMQ on Dledger Smooth Upgrade
对于用户来说,这样做最直接的好处是:若使用 Master/Slave 架构模式,一旦一个 broker 挂了,则需要手动控制。但是使用 Dledger 之后不需要这么做。因为 Dledger 可以通过自己选举,然后把选举结果直接传给 RocketMQ 的 broker,这样通过 nameserver 拿到路由的时候就可以自动找到 leader 节点去访问消息,达到自动切换的目的。如下图 12 所示。
图 12 RocketMQ on Dledger Failover
3.3 Dledger 在多中心场景下的使用
上面只是最基本的应用,更直接的应用如图 13 所示:(容灾切换)
图 13 RocketMQ on Dledger Multi Center
这里举一个真实的金融应用场景:杭州、深圳和上海分别代表系统中三个节点,进行消息传输。要求:数据一条都不能丢,满足实时高可用并且考虑城市之间的容灾。
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 来体验一下。
图 14 RocketMQon Dledger Quick Start
4. RocketMQ 社区发展
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 所示。
图 15 openmessaging-hakv
代码详见:https://github.com/openmessaging/openmessaging-hakv
目前来看,我们没有一个嵌入式且高可用的解决方案。RocksDB 可以直接用,但是它本身不支持高可用。有了 DLedger 之后,我们可以把操作的记录直接写入 DLedger,但是基于这些记录恢复过程我们可以自己选择。假如数据量少,对高可用要求高,比如元数据,我们可以直接存在内存的 hashmap 中,根据 DLedger 的写入记录来构建自己的 hashmap,从而达到数据的一致性和容灾功能。
本文作者:刘振东,GitHub ID dongeforever,阿里巴巴技术专家,2016 年中间件性能挑战赛亚军,具有丰富的分布式系统设计经验,目前负责 RocketMQ 新航道的探索和创新。
评论