以下这段内容是关于 Apache RocketMQ 中 Subscription(订阅) 的详细说明。我们来用清晰、易懂的方式,帮你全面理解“订阅”在 RocketMQ 中的含义、作用、核心规则和最佳实践。
一、什么是 Subscription(订阅)?
定义:
Subscription 是消费者获取和处理消息的规则集合,它定义了:
- 我要从哪个 Topic 消费消息?
- 我只关心哪些类型的消息?(通过过滤规则)
- 从哪里开始消费?(消费进度)
📌 类比理解:
- 想象你在微信公众号里“关注”某个账号(比如“科技早报”),这就叫“订阅”。
- 但你还想进一步筛选:只看“AI 相关”的文章 → 这就像设置了“过滤规则”。
- 系统会记住你看到哪一篇了 → 这就是“消费进度”。
✅ 在 RocketMQ 中:
- 订阅 = 消费者组(Consumer Group)对某个 Topic 的关注 + 过滤条件 + 消费位置
二、订阅的核心功能
✅ 功能 1:消息过滤(Message Filtering)
不是 Topic 里的所有消息都该被消费,你可以通过 Filter Rules 只消费感兴趣的消息。
支持两种过滤方式:
类型 | 说明 | 示例 |
---|---|---|
Tag 过滤 | 最常用,基于消息的 Tag 字段进行匹配 | consumer.subscribe("OrderTopic", "CREATE") 只消费 Tag 为 CREATE 的订单创建消息 |
SQL92 过滤 | 更强大,支持根据消息属性(Properties)做复杂条件判断 | consumer.subscribe("OrderTopic", "age > 18 AND city = 'Beijing'") |
📌 注意:
- Producer 发送消息时可以设置
Tag
和自定义Properties
。 - Broker 根据订阅规则做“服务端过滤”,不满足条件的消息根本不会推送给 Consumer,节省网络和处理资源。
✅ 功能 2:消费状态管理(Consumption Status)
- RocketMQ 默认提供 持久化订阅(Persistent Subscription)。
- 消费进度(Offset)会被保存在 Broker 上。
- 即使 Consumer 重启、宕机、扩容,也能从上次消费的位置继续消费,不会重复也不会丢失。
📌 示例:
GroupA 消费了 OrderTopic 的前 1000 条消息 → offset=1000
突然宕机 → 重启后自动从第 1001 条开始消费
⚠️ 如果你使用的是“广播模式”(Broadcasting),消费进度保存在本地文件,不推荐用于生产环境。
三、订阅的确定规则(如何唯一标识一个订阅?)
🔑 一个订阅 = Consumer Group + Topic + Filter Expression
也就是说:
场景 | 是否算不同的订阅? |
---|---|
GroupA 订阅 TopicA | ✅ 是一个订阅 |
GroupB 也订阅 TopicA | ✅ 是另一个独立的订阅(不同 Group) |
GroupA 同时订阅 TopicA 和 TopicB | ✅ 两个独立订阅 |
GroupA 订阅 TopicA 且 filter=TagA GroupA 订阅 TopicA 且 filter=TagB | ❌ 错误!同一个 Group 内不能有不同的 filter! |
📌 典型场景图解
场景 1:一个 Topic 被多个 Group 订阅(一对多)
Topic: OrderTopic
/ \
/ \
↓ ↓
Group: OrderService Group: AnalyticsService
(订阅所有订单) (只订阅支付成功的订单)
✅ 正确:不同业务系统关注同一数据源,各自独立消费。
场景 2:一个 Group 订阅多个 Topic(多对一)
Group: NotificationService
/ \
↓ ↓
Topic: UserLogin Topic: OrderPaid
(登录通知) (支付成功通知)
✅ 正确:一个服务需要响应多种事件。
四、订阅的内部属性
1. Filter Type(过滤类型)
类型 | 描述 |
---|---|
Tag | 简单字符串匹配,性能高,推荐优先使用 |
SQL92 | 支持 AND/OR/>,<,= 等复杂表达式,适合精细化过滤 |
📌 使用建议:
- 能用 Tag 就不用 SQL92,性能更好。
- SQL92 需要开启
broker
配置:enablePropertyFilter=true
2. Filter Expression(过滤表达式)
- 对于 Tag:就是具体的 Tag 名,如
"TagA"
或"TagA || TagB"
- 对于 SQL92:是符合 SQL 语法的条件表达式,如
"status = 'PAID' AND amount > 100"
📌 示例代码:
// Tag 过滤
consumer.subscribe("OrderTopic", "CREATE");
// SQL92 过滤
consumer.subscribe("OrderTopic", MessageSelector.bySql("age > 18 AND city = 'Shanghai'"));
五、行为约束:订阅一致性(Subscription Consistency)
❗这是最关键的一条原则!
同一个 Consumer Group 内的所有 Consumer 实例,必须使用完全相同的订阅配置!
✅ 正确示例(一致):
// Consumer C1
Consumer c1 = new DefaultMQPushConsumer("GROUP_A");
c1.subscribe("TopicA", "TagA");
// Consumer C2
Consumer c2 = new DefaultMQPushConsumer("GROUP_A");
c2.subscribe("TopicA", "TagA"); // ✅ 相同订阅
❌ 错误示例(不一致):
// Consumer C1
c1.subscribe("TopicA", "TagA");
// Consumer C2
c2.subscribe("TopicA", "TagB"); // ❌ 错误!同一个 Group 不能有不同的 Tag
为什么必须一致?
- RocketMQ 的负载均衡机制假设:Group 内所有 Consumer 能处理相同类型的消息。
- 如果有的 Consumer 只订阅
TagA
,有的订阅TagB
:- 可能导致某些消息没人消费(漏消费)
- 或者被错误地分配给无法处理它的 Consumer
- 甚至引发消费混乱或堆积
六、使用建议(Usage Notes)
✅ 推荐做法:
1. 提前规划订阅关系,避免频繁修改
- 订阅一旦建立,就关联了:
- 消费进度(Offset)
- 过滤规则
- 负载均衡策略
- 如果你在生产环境频繁修改订阅(比如改 Tag 或 SQL 表达式):
- 可能触发重新负载均衡
- 导致短暂的消息重复或延迟
- 客户端需要重新同步状态
📌 建议:
- 上线前设计好订阅逻辑。
- 如需变更,先下线 Consumer,修改后再重启。
2. 不要在一个 Group 内混用不同订阅
- 开发时注意:
- 所有实例的
subscribe()
调用必须一致。 - 不要因为“测试方便”在一个 Group 里跑不同逻辑。
- 所有实例的
📌 建议:
- 不同业务逻辑 → 使用不同 Consumer Group。
- 比如:
ORDER_CREATE_GROUP
→ 订阅Tag=CREATE
ORDER_CANCEL_GROUP
→ 订阅Tag=CANCEL
3. 合理使用 Tag 和 SQL92
推荐 | 不推荐 |
---|---|
用 Tag 做一级分类(如 CREATE, PAY, CANCEL) | 用 SQL92 做简单匹配(如 tag == 'A' ) |
用 SQL92 做属性过滤(如 userLevel > 3 ) | 频繁变更 SQL 表达式 |
七、总结:一句话理解 Subscription
Subscription 就是你告诉 RocketMQ:“我是谁(Group),我想消费哪个 Topic 的哪些消息(Filter),并且要接着上次的位置继续看(Offset)”。它是消息投递的“路线图”和“筛选器”。
附:订阅最佳实践清单 ✅
项目 | 推荐做法 |
---|---|
✅ 唯一性 | Group + Topic 唯一确定一个订阅 |
✅ 一致性 | 同一组内所有 Consumer 订阅必须完全一致 |
✅ 过滤 | 优先使用 Tag ,复杂场景用 SQL92 |
✅ 修改 | 避免生产环境频繁变更订阅 |
✅ 多 Topic | 一个 Group 可以订阅多个 Topic,但要管理好逻辑复杂度 |
✅ 监控 | 使用 mqadmin consumerProgress 查看订阅状态和消费延迟 |
如果你正在设计消息系统,记住这个原则:
一个业务逻辑 → 一个 Consumer Group → 一套固定的订阅规则
如果有具体问题,比如:
- 如何实现动态过滤?
- 如何处理订阅变更?
- Spring Boot 中如何配置多个订阅?
欢迎继续提问!