1、RocketMQ 核心架构拆解
1. 为什么要使用消息队列?
消息队列(MQ)是分布式系统中不可或缺的中间件,主要解决系统间的解耦、异步和削峰填谷问题。
- 解耦:生产者和消费者通过消息队列通信,彼此无需直接依赖,极大提升系统灵活性和可维护性。例如订单系统下单后,短信、积分等服务都可通过订阅消息队列实现解耦。
- 异步:将耗时操作(如发短信、邮件)异步处理,主流程快速响应,提升用户体验。
- 削峰填谷:高并发场景下,消息队列作为缓冲区,将瞬时高流量转化为后端可承受的平稳流量,防止系统崩溃。
副作用:引入消息队列会带来系统复杂度提升、可用性降低、消息重复、顺序性、分布式事务、消息堆积等问题,需要配套机制解决。
2. 为什么选择 RocketMQ?
RocketMQ 具备高吞吐、低延迟、分布式高可用、消息可靠性强、支持大规模消息堆积等特点,尤其适合金融、电商等高并发高可靠场景。其在阿里双十一等极端场景下经受住了考验。
- 单机吞吐量十万级,支持亿级消息堆积
- 分布式架构,主从高可用
- 消息可靠性高,可配置参数实现 0 丢失
- 功能完善,支持顺序、延时、事务等多种消息类型
- 源码开放,易于二次开发
3. RocketMQ 优缺点
- 优点:
- 单机吞吐量高,分布式高可用
- 消息可靠性强,支持主从同步
- 功能丰富,支持顺序、延时、事务、死信队列等
- 支持亿级消息堆积,性能稳定
- 源码开放,易于定制
4. 消息队列的消息模型
-
队列模型:生产者将消息发送到队列,多个消费者竞争消费,每条消息只会被一个消费者消费,适合任务分发、负载均衡场景。
-
发布/订阅模型:生产者将消息发送到主题(Topic),多个消费者订阅同一主题,每个消费者都能收到全量消息,适合广播、事件通知场景。
-
5、RocketMQ 核心概念详解
- Message(消息):要传输的信息。每条消息必须有一个主题(Topic),可选标签(Tag)和额外键值对(如业务 Key),便于定位和查询。
- Topic(主题):消息的归类,是消息的第一级类型。一个 Topic 可有多个生产者和消费者,彼此松耦合。
- Tag(标签):子主题,是消息的第二级类型,为同一 Topic 下不同业务目的的消息提供区分。可选,便于代码管理和消息查询。
- Group(消费组):订阅者的集合。每个消费组消费主题中一份完整消息,组内消费者竞争消费,组间互不影响。
- Message Queue(消息队列):一个 Topic 下可有多个队列,提升并发。消费者需遍历所有队列获取全部消息。
- Offset(消费位点):每个消费组在每个队列上的消费进度。消费一条消息,Offset 加一,便于消息重复消费和进度管理。
6. 消息消费模式
- 集群消费(Clustering):同一消费组内的消费者分摊消费队列中的消息,实现负载均衡。
- 广播消费(Broadcasting):同一消费组内的每个消费者都能收到全量消息,适合多副本场景。
7. RocketMQ 基本架构
RocketMQ 架构由四大核心组件组成:
- Producer:生产者,负责发送消息
- Consumer:消费者,负责消费消息
- Broker:消息存储与转发
- NameServer:注册中心,负责路由发现
每个组件都支持集群部署,保证高可用和扩展性。
8. 四大核心组件介绍
-
Producer:支持同步、异步、单向三种发送方式,适应不同业务场景。
Producer由用户进行分布式部署,消息由Producer通过多种负载均衡模式发送到Broker集群,发送低延时,支持快速失败。
RocketMQ 提供了三种方式发送消息:同步、异步和单向
1、同步发送:同步发送指消息发送方发出数据后会在收到接收方发回响应之后才发下一个数据包。一般用于重要通知消息,例如重要通知邮件、营销短信。
2、异步发送:异步发送指发送方发出数据后,不等接收方发回响应,接着发送下个数据包,一般用于可能链路耗时较长而对响应时间敏感的业务场景,例如用户视频上传后通知启动转码服务。
3、单向发送:单向发送是指只负责发送消息而不等待服务器回应且没有回调函数触发,适用于某些耗时非常短但对可靠性要求并不高的场景,例如日志收集
-
Consumer:支持 Push 和 Pull 两种消费模式,支持集群和广播消费。
消息消费者,负责消费消息,一般是后台系统负责异步消费。
1、Consumer也由用户部署,支持 PUSH 和 PULL 两种消费模式,支持集群消费和广播消费,提供实时的消息订阅机制。
2、Pull:拉取型消费者(Pull Consumer)主动从消息服务器拉取信息,只要批量拉取到消息,用户应用就会启动消费过程, 所以 Pull 称为主动消费型。
3、Push:推送型消费者(Push Consumer)封装了消息的拉取、消费进度和其他的内部维护工作,将消息到达时执行的回调接口留给用户应用程序来实现。所以 Push 称为被动消费类型,但其实从实现上看还是从消息服务器中拉取消息,不同于 Pull 的是 Push 首先要注册消费监听器,当监听器处触发后才开始消费消息。
-
Broker:负责消息存储、转发、索引、主从同步等,支持高可用部署。
1、Broker 内部维护着一个个 Consumer Queue,用来存储消息的索引,真正存储消息的地方是 CommitLog(日志文件)。2、单个 Broker 与所有的 Nameserver 保持着长连接和心跳,并会定时将 Topic 信息同步到 NameServer,和 NameServer 的通信底层是通过 Netty 实现的。
-
NameServer:无状态,支持横向扩展,负责 Broker 路由信息管理。
1、每个 NameServer 结点之间是相互独立,彼此没有任何信息交互。
2、Nameserver 被设计成几乎是无状态的,通过部署多个结点来标识自己是一个伪集群,Producer 在发送消息前从 NameServer 中获取 Topic 的路由信息也就是发往哪个 Broker,Consumer 也会定时从 NameServer 获取 Topic 的路由信息,Broker 在启动时会向 NameServer 注册,并定时进行心跳连接,且定时同步维护的 Topic 到 NameServer。
功能主要有两个:1、和 Broker 结点保持长连接。2、维护 Topic 的路由信息。
9. 如何保证消息的可用性/可靠性/不丢失?
消息可能在哪些阶段丢失呢?可能会在这三个阶段发生丢失:生产阶段、存储阶段、消费阶段。
-
生产阶段:
- 主要通过请求确认机制,确保消息可靠传递。
- 同步发送时,需处理响应结果和异常。如果返回 OK,表示消息已成功发送到 Broker;如响应失败或异常,应重试。
- 异步发送时,应在回调方法中检查发送结果,失败或异常也需重试。
- 若发生超时,可通过查询日志 API 检查消息是否已存储成功。
-
存储阶段:
- 可通过配置可靠性优先的 Broker 参数,避免宕机丢消息。高可靠场景建议使用同步刷盘。
- 消息需持久化到 CommitLog(日志文件),即使 Broker 宕机,未消费的消息也能重放。
- Broker 支持同步刷盘和异步刷盘。同步刷盘更可靠,Producer 发送消息后需等数据持久化到磁盘再返回响应。
- Broker 主从同步复制,提升可靠性。
-
消费阶段:消费端业务处理完成后再确认消费,避免消息丢失。
Consumer 保证消息成功消费的关键在于确认的时机,不要在收到消息后就立即发送消费确认,
而是应该在执行完所有消费业务逻辑之后,再发送消费确认。
因为消息队列维护了消费的位置,逻辑执行失败了,没有确认,再去队列拉取消息,就还是之前的一条
10. 如何处理消息重复?
RocketMQ 保证消息至少投递一次,无法避免重复消费。需在业务端实现幂等性或去重:
- 业务唯一标识(如订单号)+ Redis/数据库唯一索引去重
- 乐观锁/悲观锁机制防止重复处理
11. 如何处理消息积压?
- 消费者扩容:
如果当前 Topic 的 Message Queue 的数量大于消费者数量,就可以对消费者进行扩容,增加消费者,来提高消费能力,尽快把积压的消息消费玩。
- 消息迁移 Queue 扩容:
如果当前 Topic 的 Message Queue 的数量小于或者等于消费者数量,
这种情况,再扩容消费者就没什么用,就得考虑扩容 Message Queue。
可以新建一个临时的 Topic,临时的 Topic 多设置一些 Message Queue,
然后先用一些消费者把消费的数据丢到临时的 Topic,因为不用业务处理,只是转发一下消息,还是很快的。
接下来用扩容的消费者去消费新的 Topic 里的数据,消费完了之后,恢复原状。
12. 顺序消息如何实现?
- 局部顺序:同一业务 Key(如订单号)的消息通过 hash 取模发送到同一个队列,保证队列内有序。
局部顺序消息保证在某个逻辑分区或业务逻辑下的消息顺序,例如同一个订单或用户的消息按顺序消费,而不同订单或用户之间的顺序不做保证。
- 全局顺序:所有消息发送到同一个队列,保证全局顺序,但吞吐量有限。
上面的主题队列数修改成 1 , 就是全局有序了 。但是会导致不能进行水平扩充,系统吞吐量受限。