TL;DR
- 场景: JMS Topic 在应用集群中广播,导致同一业务被多节点重复消费
- 结论: 用 ActiveMQ Virtual Topic(或 JMS 2.0 Shared Subscription)把”组间广播 + 组内竞争消费”落到中间件层
- 产出: Queue/Topic 选型边界、PERSISTENT/NON_PERSISTENT 语义、集群去重方案与排错卡
JMS消息模式
点对点 (Point-to-Point)
- 基于消息队列的一对一通信模式
- 生产者发送消息到特定队列,消费者从队列提取处理
- 每条消息只能被一个消费者接收
- 典型场景:订单处理、异步任务处理
发布/订阅 (Publish/Subscribe)
- 基于主题的一对多广播式通信
- 发布者向主题发送消息,所有订阅者收到消息副本
- 支持持久订阅和非持久订阅
- 典型场景:实时通知、事件驱动架构
传递方式
NON_PERSISTENT (非持久性)
- 最多投递一次,不保证消息一定能被接收
- 消息直接发送给消费者,不在中间存储
- 优势:传输速度快,系统开销小
- 风险:服务中断时未传递消息会永久丢失
PERSISTENT (持久性)
- 保证消息投递,采用”暂存-转送”机制
- 消息首先持久化到磁盘或数据库
- 服务确认接收后才从存储删除
- 服务中断时消息保留直到恢复
应用集群问题
问题描述: 在 Queue 模式下消息消费正常(不同节点抢占消费),但 Topic 广播模式下,同一消息会被不同节点重复消费。
解决方案
方案一:创建多个相同Queue
- 弊端:存储资源浪费、生产者负担加重、扩展性差
方案二:Topic模式+业务散列/分布式锁
- 弊端:业务侵入性强、系统复杂度高、可维护性差
方案三:虚拟主题 (Virtual Topic) - 推荐
- 生产者发布到 VirtualTopic.XXX
- ActiveMQ 自动为每个消费者组创建队列 Consumer.<应用名>.VirtualTopic.XXX
- 同一消费者组内竞争消费 (P2P)
- 不同消费者组获取全量消息 (Topic)
工作原理:
生产者 -> VirtualTopic.ORDER
↓
Consumer.GroupA.VirtualTopic.ORDER (队列,组内竞争)
Consumer.GroupB.VirtualTopic.ORDER (队列,组内竞争)
错误速查表
| 症状 | 根因 | 修复方案 |
|---|---|---|
| Topic模式同一业务多台应用都执行 | Pub/Sub广播语义 | 用 Virtual Topic 或 JMS 2.0 Shared Subscription |
| 应用重启后少消息 | 默认NON_PERSISTENT;非持久订阅离线丢失 | 改 PERSISTENT;用 durable 订阅 |
| Queue模式重复处理 | 处理失败触发redelivery;ACK过早 | 检查 redelivered 标记;ACK 放业务成功后 |
| 消息堆积、延迟大 | 消费者吞吐不足 | 扩容;调整并发与 prefetch;设置 TTL+DLQ |
| DLQ持续增长 | 毒消息达到最大重试 | 增加消息 schema 兼容;做降级/隔离 |