当前位置: 首页 > 后端技术 > Java

RocketMQ 5.0 多语言客户端的设计与实现

时间:2023-04-01 21:12:12 Java

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