本文作者:古崟佑,阿里云中间件开发。
RocketMQ 5.0 版本拥有非常多新特性,比如存储计算分离、 batch 能力的提升等,它是具有里程碑意义的版本。
(相关资料图)
提到新版本,我们往往会首先想到服务端架构的设计变动,很容易忽略客户端的设计理念。客户端也是消息产品的必要组成部分,许多特性需要 client 与 server 两端互相协作,才能更好地实现。
轻量化、云原生以及统一模型是 RocketMQ 5.0 客户端的三个设计理念。
01轻量化
轻量化的重点在于轻逻辑、轻流程,化繁为简,减少多语言生态发展的阻碍。
上图列举了 RocketMQ 4.x 版本和 RocketMQ 5.0 版本的差异。
①4.x 版本的序列化使用 JsonCodecs 和 RocketMQCodecs,5.0 版本使用的则是标准的 Protobuf 协议。多语言发展的阻碍包括许多不规范,比如 RocketMQ 自定义序列化对于其他的语言需要自己实现一套协议以实现正反序列化解析。而 Json 作为标准序列化协议,基本可以实现所有语言的正反序列化,但缺点也非常明显,冗余信息过多,体积占用太大,因此更多用于 restful 架构等前后端交互的场景。此外,消息中间件场景无需关心传输数据时是否可读。因此, Protobuf 成为了选择,它原生支持多语言,且传输时体积占用非常小,成熟且标准。
②此前客户端使用 consumer 消费信息时,会存在计算逻辑比如重平衡 以及系统级 topic 处理。但是 RocketMQ 5.0 版本将所有计算逻辑上移到了服务端,客户端只需简单地调用 Receive 接口,也无需额外处理系统级 topic ,整体逻辑变得非常轻量。
③4.x版本的实现和维护成本非常高,因此5.0版本并没有基于4.x进行迭代和更新。设计之初有一种思路为直接在4.x客户端上增加 gRPC 协议,迭代升级成为5.0,但这相当于扛着历史包袱往前走,也违背了轻量化原则,因此被否定。
02云原生
云的弹性、高可用性以及交互运维能力在 RocketMQ 5.0 客户端中均有体现,分别对应极致弹性伸缩、低耦合以及云端一体。
上图为 4.x版本和5.0版本的客户端。在4.x版本中, 一个 Queue 最多对应一个 consumer,增加 consumer 不一定能提升消费并发,其弹性有上限。但在 RocketMQ 5.0,每一个 consumer 可以向所有 broker 发起 Pop 请求。此前为 push 模式,现在改为 pop 请求。提升 consumer 数量能够提升消费并发,即使只有一个队列,也可以生成数百数千个消费者统一拉取队列消息。
比如购物网站突发退货流量高峰,此前最简单有效的方式为增加业务应用数量、增加处理并发,比如增加退货系统和客户系统核心的应用数据。但如果一开始队列数设置并没有这么多,则需要先扩容队列,再扩容核心业务数。同时,扩容后也仅对新入队的消息处理速度有提升,已经造成堆积的队列消费速度依然缓慢。此时会出现一种情况,先提交的退货申请后被处理,会对用户体验造成影响。
而 RocketMQ 5.0 的 pop 消费模型只需直接增加业务处理节点数即可解决问题。
已堆积队列无法通过扩容的方式加速消费,与低耦合场景相似。比如在4.x版本中,假如某消费节点发生 FullGC ,消费比较慢,但是没有完全下线,依然与服务端保持着心跳。因此队列依然会被分配至该节点,分配队列逐渐堆积可能会引发一系列问题。
但在 RocketMQ 5.0,如果某节点发生 FullGC,分配粒度会从队列退化为消息,分配的消息消费缓慢,不会产生堆积。
因此,从极致弹性和低耦合上可以看出 RocketMQ 5.0 之后使用 Pop 协议的优越性,只需要简单地在消费能力不足时扩容、富余时缩容,单一节点故障也不会影响到全局。
云可以理解为远在机房的服务端,端则嵌在业务 SDK。此前,在控制台上进行运维操作时,只能控制服务端,对客户端的掌控非常有限,使用体验非常割裂。比如更改服务端配置,除了白屏也可以通过命令工具轻松修改配置,且及时生效。但如果客户端修改配置,则会涉及到业务发布,很容易出现问题。
RocketMQ 5.0 采用 telemetry 遥测协议与服务端进行协同。遥测协议的作用为使云和端之间通过 telemetry 进行交互,包括但不限于限流策略、重试策略、发布和订阅关系管控、可观测开关、接入点信息、堆栈信息等。主要实现方式为直接由 SDK 向服务端发起 telemetry 请求,然后往 observer 读写上图所示的指令即可。
指令类型包括设置、打印堆栈、验证消息、消费以及事务消息回查,其中比较关键的能力为可观测开关以及 OTLP 接入点信息。
以往的可观测是基于消息轨迹实现,在发送消息和消费消息时,将上下文中的参数添加进轨迹消息,在后台攒批异步发送。上图为发送消息的所有 context,包括MessageQueue、MessageID 、IP 等信息。
上图为消费时的 context 。对于单个 MessageID ,端对端会产生三条轨迹消息,分别为发送后、消费前和消费后。如果想要跟踪消息轮转轨迹,可以通过查询轨迹的消息数据找到它,因其将 MessageID 作为 key 值插入到 topic 。而此前需要在控制台根据 MessageID 查询轨迹消息。
RocketMQ 此前已经具备可观测能力,但自定义可观测方式无法很好地对接现有的可观测产品和能力。而5.0版本拥有了标准化的可观测协议,可以使用更丰富、更专业的分析和展示工具,让可观测数据有了更高的价值。
Telemetry 遥测协议是将可观测开关以及接入点下发到客户端,Proxy 会默认将接入点信息设置为自己,可以将客户端上报的所有可观测消息在服务端做收敛,再统一使用标准的 OpenTelemetry 和 Opencensu 两个协议上报到 SLS , SLS 再通过 TLog 的方式与 Prometheus、Grafana 进行对接。
Tracing 的数据链路比较复杂,也需要先经过 proxy 将可观测数据导到 proxy 上。而Tracing 的数据量较大,此前也曾考虑过直接通过用户自定义配置导出接入点,不经过 proxy ,但这会引发其他问题,比如4.x用户希望接入 RocketMQ 5.0 服务端使用可观测能力,则需要通过轨迹消息解码进行解析再通过标准化可观测协议输出。
多语言方面,最新出的 OpenTelemetry 支持不一定足够全面,因此也会需要一些旧的可观测协议比如 Opencensu,但整体来说,它们都是标准的协议。
03统一模型
此前的 RocketMQ API 缺乏精心设计的消息模型,很多概念没有明确的定义。比如发送消息时有两个 ID ,分别是用户自己定义的 MessageID 和服务端下发的 offsetID 。但是在消费时,下发的 offsetID 又变成了 MessageID ,模棱两可的定义使得预测客户行为变得更加困难。并且,此前大多以执行为导向,对开发者并不友好,许多功能是基于实现而不是接口。因此用户需要触及到很多细节,需要面对过多冗余和复杂界面,出错概率极大。
另外,发展多语言生态时,也需要统一模型引导多语言的发展实现。
主题类型是其中一个明显的改变。
此前,客户端不做 topic 类型校验,任何一种类型消息都可以发往 topic 。但是在RocketMQ 5.0 之后会对 topic 类型做校验,真正从 topic 主题类型上做了区分,比如顺序消息 topic 只能发顺序消息。此前的 delay 消息为分级别延时消息,而现在为支持毫秒级精度的定时消息,可以支持任意时间精度。
同时,模型定义了各种各样的消费者,有不同的消费者实现,比如 PushConsumer 、PullConsumer 以及 SimpleConsumer ,底层均使用 pop 协议实现。比如以前的PushConsumer 是基于手动拉的方式,而现在采用了 pop 方式。
SimpleConsumer 的使用方法非常简单,直接调用 receive message 接口,获取到消息之后进行消费,消费成功则进行 Ack;如果消费失败,则更改可见时间。整体流程非常直观,完全基于接口定义。
新版本的启动流程增加了准备工作,为了能够更早地捕捉到明显的错误以及异常,比如尝试从服务端获取设置,可以对此类设置进行热更新,称为服务器和客户遥测,任何准备失败都会导致客户端启动失败,而以往可以强行将客户端拉起,然后不断进行重试。
增加了准备工作后,配置问题或本身网络问题可以提前被发现,比如 topic 路由信息不存在,启动时会先对 bonding topic 做检查,每一个发送者、生产者可以提前设置预留 topic 信息,再根据错误信息进行检查,判断是否可以启动。