导语

在Apache CoC 2024 杭州站大会中,腾讯云高级工程师张乐为与会者带来了精彩的演讲。围绕《基于 RocketMQ 底座实现 AMQP 协议》的背景、目标、方案设计以及几个核心技术实现做了详细的阐述。

image.png

作者简介

张乐

腾讯高级工程师,负责腾讯云 RabbitMQ Serverless 商业化开发。

10年中间件开发经验,开源社区爱好者,Apollo、Spring Cloud Tencent PMC。

背景

AMQP 协议

AMQP(Advanced Message Queuing Protocol) 是 2004年左右由业界的几个大公司一起制定的高级消息队列协议,目标是跟 HTTP 一样广泛使用的面向消息的标准通信协议。

image.png

图:AMQP 模型

AMQP 核心模型

● Virtual Hosts(虚拟主机)

○ 用于多租户逻辑隔离,例如可以用于区分测试、预发、生产环境。

● Exchange(交换机)

○ 在 AMQP 协议中,生产者往 Exchange 里发送消息,消息发送到 Exchange 之后,根据 RoutingKey、BindingKey 的匹配关系把消息分发到相应的目标队列。

● Queue(队列)

○ 一组消息的集合,消费者通过拉或者推的方式从队列里消费消息。

目前市面上最主流的 AMQP 实现者是 RabbitMQ,实现的 AMQP 协议版本是 0.9.1。在 Kafka、RocketMQ 、Pulsar 等新型消息队列产品问世之前,RabbitMQ 是最流行的消息队列之一。所以 RabbitMQ 在国内外还是有大量的公司使用,例如传统的金融行业。

RabbitMQ 协议的局限性

腾讯云托管了开源版本的 RabbitMQ,为用户提供开箱即用的 RabbitMQ 产品。在运维成百上千套 RabbitMQ 集群过程中,我们也发现了开源 RabbitMQ 的一些局限性,例如以下列出的几点:

抗堆积能力弱

通常 RabbitMQ 集群由三副本组成的一个分布式集群,通过副本之间的消息复制保证消息的高可靠性。不能做到无限的水平扩容,提升集群的服务水位。导致了集群抗堆积能力弱、单集群的 TPS 上限有限。所以 RabbitMQ 一定程度上不适用于大规模的消息队列场景。

网络分区问题

RabbitMQ 支持 Raft 模式消息复制,在 Raft 模式下,容易导致网络分区的问题。

二次开发、运维难

RabbitMQ 使用 Erlang 小众语言开发,导致二次开发门槛极高,只能通过调参、重启等比较被动的方式运维 RabbitMQ。

延迟消息插件不稳定

延迟消息在消息队列产品中是使用非常广泛的功能,但是在 RabbitMQ 实现中,延迟消息插件打开之后,很容易导致集群出现 OOM 等不稳定的情况。

技术选型

基于 AMQP 的广泛性以及开源 RabbitMQ 的局限性,我们的目标是非常明确的。要实现一套完全自主可控、高可扩展性、低运维成本的 AMQP 协议的消息队列产品。

我们最终选择了基于 RocketMQ 实现 AMQP 协议,主要有下面几个点的考虑:

  1. 完全自研一套 MQ 产品,成本高、时间周期长、稳定性难保证。
  2. 目前 RocketMQ 在在线消息领域使用非常广泛,并且新版本的 RocketMQ 提供 了 LMQ、POP 等特性,使其在适配 AMQP 协议的复杂度上大大降低。并且 RocketMQ 经过多年的发展,在稳定性、性能、功能上表现都非常优异。
  3. 腾讯云有专门的 RocketMQ 团队,提供了技术保障。

AMQP On RocketMQ 系统架构

image.png

图:架构图

服务端侧一共分为两层。

1.  RocketMQ 存储池

a.  由多套 RocketMQ Broker 组成的消息存储池,可以通过水平垂直扩容单个 Broker 集群,或者增加更多的 Broker 集群,达到无限容量的效果。

2.  Proxy 适配层

a.  在 Proxy 层适配了 AMQP 协议, 一个 Proxy 集群等于一个 AMQP 集群。

b.  几乎无状态,可以水平横向无限扩容。

c.  一个 Proxy 集群只会绑定到一个底层的 Broker 集群。

d.  Proxy 元数据存储在数据库里,多个 Proxy 集群共享数据库,通过 Proxy Id 逻辑隔离。

这种架构和最新的 RocketMQ 5 架构完全一致,可以认为是存算分离架构,便于实现 Serverless 产品形态。

核心技术点

模型映射

LMQ(Light Message Queue)

在 RocketMQ 模型中 Topic 是最核心的模型,同时也非常的”重“,消耗的资源多。当 Topic 数量增加到万级别之后,Broker 的稳定性会有比较大的影响。在 AMQP On RocketMQ 模型中, Broker 作为上层 Proxy 的共享集群,如果 AMQP 的 Queue 映射到 RocketMQ 的 Topic 就会产生大量的 Topic。RocketMQ 为了解决这个问题,支持 LMQ(Light Message Queue)队列,这种队列更加轻量,能支持到百万级别的量级。

LMQ 实现的原理是基于 RocketMQ 比较灵活的消息存储模型,如下图所示:

image.png

图:RocketMQ 存储模型

所有 Topic 的消息都追加写入到同一个 CommitLog 文件中,并同时维护消息索引文件组成消息队列。

通过这个模型,很容易扩展出 LMQ 能力。只需要在每条消息上增加一个属性(lmq)标识这条消息归属于哪些队列。然后通过属性就能反向聚合出队列的消息集合,如下图所示:

image.png

图:LMQ 模型

这种模型下,一个LMQ只是多维护了一个队列索引文件,所以非常的轻量。

最终的模型映射如下所示:

● 一个 Proxy 集群映射到 RocketMQ 的一个 Topic。

● AMQP 中的 Queue 映射到 RocketMQ 的一个 LMQ。

image.png

图:模型映射

收发消息核心链路

发送消息

  1. SDK 发送消息到 Proxy 层,Proxy 根据 Exchange 的 BindingKey 计算目标队列集合。
  2. Proxy 层通过 RocketMQ SDK 向 RocketMQ Topic=proxyId 发送一条消息,并且消息携带 lmq=List属性。

image.png

图:发送消息

消费消息

AMQP 支持两种消费模式拉和推。

image.png

图:消费模型

拉模式

拉模式相比于推模式更加简单,直接在 Proxy 层把 AMQP 的拉消息请求转化成 RocketMQ 的一个 POP 请求,完全无状态,透明转发即可。

推模式

推模式复杂一些,需要在 Proxy 层实现拉转推的逻辑。Proxy 层内置定时从 RocketMQ POP 消息的线程池,获取到消息后,通过长连接把消息推送给客户端。

二进制协议解析

AMQP 自定了数据帧的二进制格式,如下所示:

image.png

图:帧格式

在 Proxy 层需要解码、编码二进制包,这部分的工作还是非常复杂的,很容易出现不兼容的问题。因为我们的 Proxy 是用 Java 语言实现的,正好 Java 语言也有一个 AMQP 实现(QPID)已经做了这部分的工作,我们只需要实现 QPID 定义的协议接口指令即可,这样可以大大简化我们的开发工作量。

image.png

功能&性能

因为 AMQP 协议非常的复杂,目前我们实现的版本适配了绝大部分的核心功能。

FunctionAMQP On RocketMQRabbitMQ
Delay MessageSupportedPlugin (Unstable)
Retry MessageSupportedNot Supported
Message TrackSupportedPlugin
Transaction MessageNot SupportedSupported
Sequential messageNot SupportedSupported
Priority MessageNot SupportedSupported

性能上目前只做了初步的性能调优,基本能满足绝大部分用户的需求,后续我们会做更加深入的性能调优。

image.png

图:压测数据

未来展望

我们计划 25年 Q1 在腾讯云上线 RabbitMQ Serverless 形态,逐步开放给内外部用户公测使用。并在 25年年底把产品打磨的更加稳定、成熟,和开源托管版形成互补,满足不同体量的腾讯云用户诉求。

在开源方面,我们会在某一个合适的时机开源出来,一方面回馈社区,另外一方面通过社区的力量把产品打磨的更加完善。


腾讯云中间件
0 声望6 粉丝

关注云原生,分享腾讯云中间件技术