解读 RocketMQ 5.0 全新的高可用设计

2023年 7月 19日 60.8k 0

作者:斜阳

高可用架构演进背景

在分布式系统中不可避免的会遇到网络故障,机器宕机,磁盘损坏等问题,为了向用户不中断且正确的提供服务,要求系统有一定的冗余与容错能力。RocketMQ 在日志,统计分析,在线交易,金融交易等丰富的生产场景中发挥着至关重要的作用,而不同环境对基础设施的成本与可靠性提出了不同的诉求。在 RocketMQ v4 版本中有两种主流高可用设计,分别是主备模式的无切换架构和基于 Raft 的多副本架构(图中左侧和右侧所示)。生产实践中我们发现,两副本的冷备模式下备节点资源利用率低,主宕机时特殊类型消息存在可用性问题;而 Raft 高度串行化,基于多数派的确认机制在扩展只读副本时不够灵活,无法很好的支持两机房对等部署,异地多中心等复杂场景。RocketMQ v5 版本融合了上述方案的优势,提出 DLedger Controller 作为管控节点(中间部分所示),将选举逻辑插件化并优化了数据复制的实现。

image

如何实现高可用系统

副本组与数据分片

在 Primary-Backup 架构的分布式系统中,一份数据将被复制成多个副本来避免数据丢失。处理相同数据的一组节点被称为副本组(ReplicaSet),副本组的粒度可以是单个文件级别的(例如 HDFS),也可以是分区级 / 队列级的(例如 Kafka),每个真实存储节点上可以容纳若干个不同副本组的副本,也可以像 RocketMQ 一样粗粒度的独占节点。独占能够显著简化数据写入时确保持久化成功的复杂度,因为每个副本组上只有主副本会响应读写请求,备机一般配置只读来提供均衡读负载,选举这件事儿等价于让副本组内一个副本持有独占的写锁。

RocketMQ 为每个存储数据的 Broker 节点配置 ClusterName,BrokerName 标识来更好的进行资源管理。多个 BrokerName 相同的节点构成一个副本组。每个副本还拥有一个从 0 开始编号,不重复也不一定连续的 BrokerId 用来表示身份,编号为 0 的节点是这个副本组的 Leader / Primary / Master,故障时通过选举来重新对 Broker 编号标识新的身份。例如 BrokerId = {0, 1, 3},则 0 为主,其他两个为备。

一个副本组内,节点间共享数据的方式有多种,资源的共享程度由低到高来说一般有 Shared Nothing,Shared Disk,Shared Memory,Shared EveryThing。典型的 Shared Nothing 架构是 TiDB 这类纯分布式的数据库,TiDB 在每个存储节点上使用基于 RocksDB 封装的 TiKV 进行数据存储,上层通过协议交互实现事务或者 MVCC。相比于传统的分库分表策略来说,TiKV 易用性和灵活程度很高,更容易解决数据热点与伸缩时数据打散的一系列问题,但实现跨多节点的事务就需要涉及到多次网络的通信。另一端 Shared EveryThing 的案例是 AWS 的 Aurora,Aliyun 的 PolarStore,旁路 Kernal 的方式使应用完全运行于用户态,以最大程度的存储复用来减少资源消耗,一主多备完全共用一份底层可靠的存储,实现一写多读,快速切换。

大多数 KV 操作都是通过关键字的一致性哈希来计算所分配的节点,当这个节点所在的主副本组产生存储抖动,主备切换,网络分区等情况下,这个分片所对应的所有键都无法更新,局部会有一些操作失败。消息系统的模型有所不同,流量大但跨副本组的数据交互极少,无序消息发送到预期分区失败时还可以向其他副本组(分片)写入,一个副本组的故障不影响全局,这在整体服务的层面上额外提供了跨副本组的可用性。此外,考虑到 MQ 作为 Paas 层产品,被广泛部署于 Windows,Linux on Arm 等各种环境,只有减少和 Iaas 层产品的深度绑定,才能提供更好的灵活性。这种局部故障隔离和轻依赖的特性是 RocketMQ 选则 Shared Nothing 模型重要原因。

副本组中,各个节点处理的速度不同,也就有了日志水位的概念。Master 和与其差距不大的 Slave 共同组成了同步副本集(SyncStateSet)。如何定义差距不大呢?衡量的指标可以是日志水位(文件大小)差距较小,也可以是备落后的时间在一定范围内。在主宕机时,同步副本集中的其余节点有机会被提升为主,有时需要对系统进行容灾演练,或者对某些机器进行维护或灰度升级时希望定向的切换某一个副本成为新主,这又产生了优先副本(PriorityReplica)的概念。选择优先副本的原则和策略很多,可以动态选择水位最高,加入时间最久或 CommitLog 最长的副本,也可以支持机架,可用区优先这类静态策略。

从模型的角度来看,RocketMQ 单节点上 Topic 数量较多,如果像 kafka 以 topic / partition 粒度维护状态机,节点宕机会导致上万个状态机切换,这种惊群效应会带来很多潜在风险,因此 v4 版本时 RocketMQ 选择以单个 Broker 作为切换的最小粒度来管理,相比于其他更细粒度的实现,副本身份切换时只需要重分配 Broker 编号,对元数据节点压力最小。由于通信的数据量少,可以加快主备切换的速度,单个副本下线的影响被限制在副本组内,减少管理和运维成本。这种实现也一些缺点,例如存储节点的负载无法以最佳状态在集群上进行负载均衡,Topic 与存储节点本身的耦合度较高,水平扩展一般会改变分区总数,这就需要在上层附加额外的处理逻辑。

image.png

为了更规范更准确的衡量副本组的可用性指标,学术上就引入了几个名词:

  • RTO(Recovery Time Objective)恢复时间目标,一般表示业务中断到恢复的时间。
  • RPO(Recovery Point Object)恢复点目标,用于衡量业务连续性。例如某个硬盘每天备份,故障时丢失最近备份后的所有更新。
  • SLA(Service-Level Agreement)服务等级协议,厂商以合约的形式对用户进行服务质量承诺,SLA 越高通常成本也越高。

节点数量与可靠性关系密切,根据不同生产场景,RocketMQ 的一个副本组可能会有 1,2,3,5 个副本。

  • 单副本成本最低,维护最简单,宕机时其他副本组接管新消息的写入,但已写入的数据无法读取,造成部分消息消费延迟。底层硬件故障还可能导致数据永久丢失,一般用于非关键日志,数据采集等低可靠性成本诉求较强的场景。
  • 两副本较好的权衡了数据冗余的成本与性能,RocketMQ 跨副本组容灾的特性使得两副本模式适用于绝大部分 IOPS 比较高的场景。此时备机可以分摊一定的读压力(尤其是主副本由于内存紧张或者产生冷读时)。两副本由于不满足多数派(quorum)原则,没有外部系统的参与时,故障时无法进行选举切换。
  • 三副本和五副本是业界使用最为广泛的,精心设计的算法使得多数情况下系统可以自愈。基于 Paxos / Raft 属于牺牲高可用性来保证一致性的 CP 型设计,存储成本很高,容易受到 IO 分布不均匀和水桶效应的影响。每条数据都需要半数以上副本响应的设计在需要写透(write through)多副本的消息场景下不够灵活。
  • 日志复制还是消息复制

    如何保证副本组中数据的最终一致性?那肯定是通过数据复制的方式实现,我们该选择逻辑复制还是物理复制呢?

    逻辑复制: 使用消息来进行同步的场景也很多,各种 connector 实现本质上就是把消息从一个系统挪到另外一个系统上,例如将数据导入导出到 ES,Flink 这样的系统上进行分析,根据业务需要选择特定 Topic / Tag 进行同步,灵活程度和可扩展性非常高。这种方案随着 Topic 增多,系统还会有服务发现,位点和心跳管理等上层实现造成的性能损失。因此对于消息同步的场景,RocketMQ 也支持以消息路由的形式进行数据转移,将消息复制作为业务消费的特例来看待。

    物理复制: 大名鼎鼎的 MySQL 对于操作会记录逻辑日志(bin log)和重做日志(redo log)两种日志。其中 bin log 记录了语句的原始逻辑,比如修改某一行某个字段,redo log 属于物理日志,记录了哪个表空间哪个数据页改了什么。在 RocketMQ 的场景下,存储层的 CommitLog 通过链表和内核的 MappedFile 机制抽象出一条 append only 的数据流。主副本将未提交的消息按序传输给其他副本(相当于 redo log),并根据一定规则计算确认位点(confirm offset)判断日志流是否被提交。这种方案仅使用一份日志和位点就可以保证主备之间预写日志的一致性,简化复制实现的同时也提高了性能。

    为了可用性而设计的多副本结构,很明显是需要对所有需要持久化的数据进行复制的,选择物理复制更加节省资源。RocketMQ 在物理复制时又是如何保证数据的最终一致性呢?这就涉及到数据的水位对齐。对于消息和流这样近似 FIFO 的系统来说,越近期的消息价值越高,消息系统的副本组的单个节点不会像数据库系统一样,保留这个副本的全量数据,Broker 一方面不断的将冷数据规整并转入低频介质来节约成本,同时对热数据盘上的数据也会由远及近滚动删除。如果副本组中有副本宕机较久,或者在备份重建等场景下就会出现日志流的不对齐和分叉的复杂情况。在下图中我们将主节点的 CommitLog 的首尾位点作为参考点,这样就可以划分出三个区间。在下图中以蓝色箭头表示。排列组合一下就可以证明备机此时的 CommitLog 一定满足下列 6 种情况之一。

    image

    下面对每种情况进行讨论与分析:

    • 1-1 情况下满足备 Max

    相关文章

    KubeSphere 部署向量数据库 Milvus 实战指南
    探索 Kubernetes 持久化存储之 Longhorn 初窥门径
    征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
    那些年在 Terraform 上吃到的糖和踩过的坑
    无需 Kubernetes 测试 Kubernetes 网络实现
    Kubernetes v1.31 中的移除和主要变更

    发布评论