Article / 文章中心

RocketMQ 端云一体化设计与实践

发布时间:2022-02-16 点击数:599

作者 | 悟幻

一体化布景

不止于分发

咱们都知道以 RocketMQ 为代表的音讯(行列)起源于不同应用服务之间的异步解耦通讯,与以 Dubbo 为代表的 RPC 类服务通讯一同承载了分布式体系(服务)之间的通讯场景,所以服务间的音讯分发是音讯的根底诉求。可是咱们看到,在音讯(行列)这个范畴,近些年咱们业界有个很重要的趋势,便是依据音讯这份数据可以扩展到流批核算、事情驱动等不同场景,如 RocketMQ-streams,Kafka-Streams、Rabbit-Streams 等等。

不止于服务端

传统的音讯行列 MQ 首要应用于服务(端)之间的音讯通讯,比方电商范畴的买卖音讯、付出音讯、物流音讯等等。可是在音讯这个大类下,还有一个非常重要且常见的音讯范畴,即终端音讯。音讯的本质便是发送和承受,终端和服务端并没有本质上的大差异。

在这里刺进图片描绘

一体化价值

假如可以有一个一致的音讯体系(产品)来提供多场景核算(如 stream、event)、多场景(IoT、APP)接入,其实是非常有价值的,由于音讯也是一种重要数据,数据假如只存在一个体系内,可以最大地下降存储本钱,一起可以有效地避免数据因在不同体系间同步带来的一致性难题。

终端音讯剖析

本文将首要描绘的是终端音讯和服务端音讯一体化规划与实践问题,所以首先咱们对面向终端的这一大类音讯做一下根本剖析。

场景介绍

近些年,咱们看到跟着智能家居、工业互联而鼓起的面向 IoT 设备类的音讯正在呈爆破式增加,而现已发展十余年的移动互联网的手机 APP 端音讯仍然是数量级庞大。面向终端设备的音讯数量级比传统服务端的音讯要大许多量级,并仍然在快速增加。

特性剖析

虽然无论是终端音讯仍是服务端音讯,其本质都是音讯的发送和承受,可是终端场景仍是有和服务端不太相同的特色,下面扼要剖析一下:
在这里刺进图片描绘

  • 轻量

服务端一般都是运用很重的客户端 SDK 封装了许多功用和特性,可是终端由于运转环境受限且杂乱有必要运用轻量简洁的客户端 SDK。

  • 规范协议

服务端正是由于有了重量级客户端 SDK,其封装了包括协议通讯在内的全部功用,乃至可以弱化协议的存在,运用者无须感知,而终端场景由于要支撑各类杂乱的设备和场景接入,有必要要有个规范协议界说。

  • P2P

服务端音讯假如一台服务器处理失利可以由别的一台服务器处理成功即可,而终端音讯有必要清晰发给详细终端,若该终端处理失利则有必要一直重试发送该终端直到成功,这个和服务端很不相同。

  • 播送比

服务端音讯比方买卖体系发送了一条订单音讯,或许有如营销、库存、物流等几个体系感兴趣,而终端场景比方群聊、直播或许成千上万的终端设备或用户需求收到。

  • 海量接入

终端场景接入的是终端设备,而服务端接入的便是服务器,前者在量级上肯定远大于后者。

架构与模型

音讯根底剖析

完成一体化前咱们先从理论上剖析一下问题和可行性。咱们知道,无论是终端音讯仍是服务端音讯,其实便是一种通讯方法,从通讯的层面看要处理的根底问题简略总结便是:协议、匹配、触达。

  • 协议

协议便是界说了一个交流言语频道,通讯两边可以听懂内容语义。在终端场景,现在业界广泛运用的是 MQTT 协议,起源于物联网 IoT 场景,OASIS 联盟界说的规范的开放式协议。

在这里刺进图片描绘

MQTT 协议界说了是一个 Pub/Sub 的通讯模型,这个与 RocketMQ 相似的,不过其在订阅方法上比较灵敏,可以支撑多级 Topic 订阅(如 “/t/t1/t2”),可以支撑通配符订阅(如 “/t/t1/+”)

  • 匹配

匹配便是发送一条音讯后要找到一切的承受者,这个匹配查找进程是不可或缺的。

在这里刺进图片描绘

在 RocketMQ 里边实际上有这个相似的匹配进程,其经过将某个 Queue 经过 rebalance 方法分配到消费组内某台机器上,音讯经过 Queue 就直接对应上了消费机器,再经过订阅过滤(Tag 或 SQL)进行精准匹配消费者。之所以经过 Queue 就可以匹配消费机器,是由于服务端场景音讯并不需求清晰指定某台消费机器,一条音讯可以放到恣意 Queue 里边,并且恣意一台消费机器对应这个 Queue 都可以,音讯不需求清晰匹配消费机器。

而在终端场景下,一条音讯有必要清晰指定某个承受者(设备),有必要精确找到一切承受者,并且终端设备一般只会连到某个后端服务节点即单连接,和音讯发生的节点不是同一个,有必要有个较杂乱的匹配查找方针的进程,还有如 MQTT 通配符这种更灵敏的匹配特性。

  • 触达

触达即经过匹配查找后找到一切的承受者方针,需求将音讯以某种牢靠方法发给承受者。常见的触发方法有两种:Push、Pull。Push,即服务端自动推送音讯给终端设备,自动权在服务端侧,终端设备经过 ACK 来反馈音讯是否成功收到或处理,服务端需求依据终端是否回来 ACK 来决议是否重投。Pull,即终端设备自动来服务端获取其一切音讯,自动权在终端设备侧,一般经过位点 Offset 来顺次获取音讯,RocketMQ 便是这种音讯获取方法。

对比两种方法,咱们可以看到 Pull 方法需求终端设备自动办理音讯获取逻辑,这个逻辑其实有必定的杂乱性(可以参阅 RocketMQ 的客户端办理逻辑),而终端设备运转环境和条件都很杂乱,不太习惯较杂乱的 Pull 逻辑完成,比较适合被迫的 Push 方法。

别的,终端音讯有一个很重要的差异是牢靠性确保的 ACK 有必要是详细到一个终端设备的,而服务端音讯的牢靠性在于只要有一台消费者机器成功处理即可,不太关心是哪台消费者机器,音讯的牢靠性 ACK 标识可以会集在消费组维度,而终端音讯的牢靠性 ACK 标识需求详细离散到终端设备维度。简略地说,一个是客户端设备维度的 Retry 行列,一个是消费组维度的 Retry 行列。

模型与组件

依据前面的音讯根底一般性剖析,咱们来规划音讯模型,首要是要处理好匹配查找和牢靠触达两个中心问题。

  • 行列模型

音讯可以牢靠性触达的条件是要牢靠存储,音讯存储的目的是为了让承受者能获取到音讯,承受者一般有两种音讯检索维度:
1)依据订阅的主题 Topic 去查找音讯;
2)依据订阅者 ID 去查找音讯。这个便是业界常说的扩大模型:读扩大、写扩大。
在这里刺进图片描绘

读扩大:即音讯按 Topic 进行存储,承受者依据订阅的 Topic 列表去相应的 Topic 行列读取音讯。

写扩大:即音讯别离写到一切订阅的承受者行列中,每个承受者读取自己的客户端行列。

可以看到读扩大场景下音讯只写一份,写到 Topic 维度的行列,但承受者读取时需求依照订阅的 Topic 列表多次读取,而写扩大场景下音讯要写多份,写到一切承受者的客户端行列里边,明显存储本钱较大,但承受者读取简略,只需读取自己客户端一个行列即可。

咱们采用的读扩大为主,写扩大为辅的策略,由于存储的本钱和效率对用户的体感最明显。写多份不只加大了存储本钱,一起也对功能和数据精确一致性提出了应战。可是有一个当地咱们运用了写扩大模式,便是通配符匹配,由于承受者订阅的是通配符和音讯的 Topic 不是相同的内容,承受者读音讯时无法反推出音讯的 Topic,因此需求在音讯发送时依据通配符的订阅多写一个通配符行列,这样承受者直接可以依据其订阅的通配符行列读取音讯。

在这里刺进图片描绘

上图描绘的承受咱们的行列存储模型,音讯可以来自各个接入场景(如服务端的 MQ/AMQP,客户端的 MQTT),但只会写一份存到 commitlog 里边,然后分发出多个需求场景的行列索引(ConsumerQueue),如服务端场景(MQ/AMQP)可以依照一级 Topic 行列进行传统的服务端消费,客户端 MQTT 场景可以依照 MQTT 多级 Topic 以及通配符订阅进行消费音讯。

这样的一个行列模型就可以一起支撑服务端和终端场景的接入和音讯收发,抵达一体化的方针。

  • 推拉模型

介绍了底层的行列存储模型后,咱们再详细描绘一下匹配查找和牢靠触达是怎样做的。
在这里刺进图片描绘

上图展示的是一个推拉模型,图中的 P 节点是一个协议网关或 broker 插件,终端设备经过 MQTT 协议连到这个网关节点。音讯可以来自多种场景(MQ/AMQP/MQTT)发送过来,存到 Topic 行列后会有一个 notify 逻辑模块来实时感知这个新音讯抵达,然后会生成音讯事情(便是音讯的 Topic 称号),将该事情推送至网关节点,网关节点依据其连上的终端设备订阅状况进行内部匹配,找到哪些终端设备能匹配上,然后会触发 pull 恳求去存储层读取音讯再推送终端设备。

一个重要问题,便是 notify 模块怎样知道一条音讯在哪些网关节点上面的终端设备感兴趣,这个其实便是要害的匹配查找问题。一般有两种方法:1)简略的播送事情;2)会集存储在线订阅联系(如图中的 lookup 模块),然后进行匹配查找再精准推送。事情播送机制看起来有扩展性问题,可是其实功能并不差,由于咱们推送的数据很小便是 Topic 称号,并且相同 Topic 的音讯事情可以合并成一个事情,咱们线上便是默许采用的这个方法。会集存储在线订阅联系,这个也是常见的一种做法,如保存到 Rds、Redis 等,但要确保数据的实时一致性也有难度,并且要进行匹配查找对整个音讯的实时链路 RT 开支也会有必定的影响。

牢靠触达及实时性这块,上图的推拉进程中首先是经过事情告诉机制来实时奉告网关节点,然后网关节点经过 Pull 机制来换取音讯,然后 Push 给终端设备。Pull+Offset 机制可以确保音讯的牢靠性,这个是 RocketMQ 的传统模型,终端节点被迫承受网关节点的 Push,处理了终端设备轻量问题,实时性方面由于新音讯事情告诉机制而得到确保。

上图中还有一个 Cache 模块用于做音讯行列 cache,由于在大播送比场景下假如为每个终端设备都去发起行列 Pull 恳求则对 broker 读压力较大,既然每个恳求都去读取相同的 Topic 行列,则可以复用本地行列 cache。

  • lookup组件

上面的推拉模型经过新音讯事情告诉机制来处理实时触达问题,事情推送至网关的时分需求一个匹配查找进程,虽然简略的事情播送机制可以抵达必定的功能要求,但毕竟是一个播送模型,在大规模网关节点接入场景下仍然有功能瓶颈。别的,终端设备场景有许多状况查询诉求,如查找在线状况,连接互踢等等,仍然需求一个 KV 查找组件,即 lookup。

咱们当然可以运用外部 KV 存储如 Redis,但咱们不能假定体系(产品)在用户的交给环境,尤其是专有云的特别环境必定有牢靠的外部存储服务依靠。

这个 lookup 查询组件,实际上便是一个 KV 查询,可以理解为是一个分布式内存 KV,但要比分布式 KV 完成难度至少低一个等级。咱们回想一下一个分布式 KV 的根本要素有哪些:

在这里刺进图片描绘

如上图所示,一般一个分布式 KV 读写流程是,Key 经过 hash 得到一个逻辑 slot,slot 经过一个映射表得到详细的 node。Hash 算法一般是固定模数,映射表一般是会集式装备或运用一致性协议来装备。节点扩缩一般经过调整映射表来完成。

分布式 KV 完成一般有三个根本要害点:

1)映射表一致性
读写都需求依据上图的映射表进行查找节点的,假如规矩不一致数据就乱了。映射规矩装备自身可以经过会集存储,或者 zk、raft 这类协议确保强一致性,可是新旧装备的切换不能确保节点一起进行,仍然存在不一致性窗口。

2)多副本
经过一致性协议同步存储多个备份节点,用于容灾或多读。

3)负载分配
slot 映射 node 便是一个分配,要确保 node 负载均衡,比方扩缩状况或许要进行 slot 数据搬迁等。

咱们首要查询和保存的是在线状况数据,假如存储的 node 节点宕机丢掉数据,咱们可以即时重建数据,由于都是在线的,所以不需求考虑多副本问题,也不需求考虑扩缩状况 slot 数据搬迁问题,由于可以直接丢掉重建,只需求确保要害的一点:映射表的一致性,并且咱们有一个兜底机制——播送,当分片数据不牢靠或不可用时退化到播送机制。

架构规划

依据前面的理论和模型剖析介绍,咱们在考虑用什么架构形态来支撑一体化的方针,咱们从分层、扩展、交给等方面进行一下描绘。

  • 分层架构

在这里刺进图片描绘

咱们的方针是希望依据 RocketMQ 完成一体化且自闭环,但不希望 Broker 被侵入更多场景逻辑,咱们笼统了一个协议核算层,这个核算层可以是一个网关,也可以是一个 broker 插件。Broker 专心处理 Queue 的事情以及为了满足上面的核算需求做一些 Queue 存储的适配或改造。协议核算层担任协议接入,并且要可插拔布置。

  • 扩展规划

在这里刺进图片描绘

咱们都知道音讯产品归于 PaaS 产品,与上层 SaaS 事务贴得最近,为了习惯事务的不同需求,咱们大致整理一下要害的中心链路,在上下行链路上添加一些扩展点,如鉴权逻辑这个最偏事务化的逻辑,不同的事务需求都不相同,又比方 Bridge 扩展,其可以把终端设备状况和音讯数据与一些外部生态体系(产品)打通。

  • 交给规划

好的架构规划仍是要考虑终究的落地问题,即怎样交给。现在面临的现状是公共云、专有云,乃至是开源等各种环境条件的落地,应战非常大。其间最大的应战是外部依靠问题,假如产品要强依靠一个外部体系或产品,那对整个交给就会有非常大的不确定性。

为了应对各种杂乱的交给场景,一方面会规划好扩展接口,依据交给环境条件进行适配完成;另一方面,咱们也会尽或许对一些模块提供默许内部完成,如上文提到的 lookup 组件,重复造轮子也是不得已而为之,这个或许便是做产品与做平台的最大差异。

一致存储内核

前面临整个协议模型和架构进行了详细介绍,在 Broker 存储层这块还需求进一步的改造和适配。咱们希望依据 RocketMQ 一致存储内核来支撑终端和服务端的音讯收发,完成一体化的方针。

在这里刺进图片描绘

前面也提到了终端音讯场景和服务端一个很大的差异是,终端有必要要有个客户端维度的行列才能确保牢靠触达,而服务端可以运用会集式行列,由于音讯随意哪台机器消费都可以,可是终端音讯有必要清晰牢靠推送给详细客户端。客户端维度的行列意味着数量级上比传统的 RocketMQ 服务端 Topic 行列要大得多。

别的前面介绍的行列模型里边,音讯也是依照 Topic 行列进行存储的,MQTT 的 Topic 是一个灵敏的多级 Topic,客户端可以恣意生成,而不像服务端场景 Topic 是一个很重的元数据强办理,这个也意味着 Topic 行列的数量级很大。

海量行列

咱们都知道像 Kafka 这样的音讯行列每个 Topic 是独立文件,可是跟着 Topic 增多音讯文件数量也增多,次序写就退化成了随机写,功能下降明显。RocketMQ 在 Kafka 的根底上进行了改进,运用了一个 Commitlog 文件来保存一切的音讯内容,再运用 CQ 索引文件来表明每个 Topic 里边的音讯行列,由于 CQ 索引数据较小,文件增多对 IO 影响要小许多,所以在行列数量上可以抵达十万级。可是这终端设备行列场景下,十万级的行列数量仍是太小了,咱们希望进一步提升一个数量级,抵达百万级行列数量,咱们引入了 Rocksdb 引擎来进行 CQ 索引分发。

在这里刺进图片描绘

Rocksdb 是一个广泛运用的单机 KV 存储引擎,具有高功能的次序写能力。由于咱们有了 commitlog 已具有了音讯次序流存储,所以可以去掉 Rocksdb 引擎里边的 WAL,依据 Rocksdb 来保存 CQ 索引。在分发的时分咱们运用了 Rocksdb 的 WriteBatch 原子特性,分发的时分把当时的 MaxPhyOffset 注入进去,由于 Rocksdb 可以确保原子存储,后续可以依据这个 MaxPhyOffset 来做 Recover 的 checkpoint。咱们提供了一个 Compaction 的自界说完成,来进行 PhyOffset 的承认,以整理已删去的脏数据。
在这里刺进图片描绘

轻量Topic

咱们都知道 RocketMQ 中的 Topic 是一个重要的元数据,运用前要提前创立,并且会注册到 namesrv 上,然后经过 Topicroute 进行服务发现。前面说了,终端场景订阅的 Topic 比较灵敏可以恣意生成,假如依据现有的 RocketMQ 的 Topic 重办理逻辑明显有些困难。咱们界说了一种轻量的 Topic,专门支撑终端这种场景,不需求注册 namesrv 进行办理,由上层协议逻辑层进行自办理,broker 只担任存储。

总结

本文首先介绍了端云音讯场景一体化的布景,然后重点剖析了终端音讯场景特色,以及终端音讯场景支撑模型,最后对架构和存储内核进行了论述。咱们希望依据 RocketMQ 一致内核一体化支撑终端和服务端不同场景的音讯接入方针,以可以给运用者带来一体化的价值,如下降存储本钱,避免数据在不同体系间同步带来的一致性应战。