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 兼容;做降级/隔离