跳转到正文

ActiveMQ Classic 路由与分发 ​

本页结论:Classic 的路由核心是两类原生目的地——Queue(竞争消费)与 Topic(发布订阅);在此之上用 selector 做内容过滤、JMSXGroupID 做同组粘连、VirtualTopics 让 Topic 拥有竞争消费能力。

Queue 与 Topic ​

目的地语义对应心智
Queue消息被一个消费者取走,确认后删除任务队列 / Kafka 消费组
Topic每个(durable)订阅各得一份发布订阅 / Kafka 多消费组
  • 目的地首次被生产或消费时自动创建,没有显式建队命令——拼写错误会留下一堆野队列,生产需要命名规约与控制台巡检兜底(本仓库 cli-tools 实验实测了自动创建)。
  • Topic 的非 durable 订阅随连接消失;需要离线补收必须用 durable subscription。

竞争消费与分发方式 ​

  • 同一 Queue 上多个消费者:Broker 轮转分发;消费者越多吞吐越高,但全局处理顺序不再保证。
  • 消费端 receive/监听器拉取,预取缓冲由消费者 prefetch 控制(prefetch 越大吞吐越高、再均衡时未确认消息越多)。
  • 慢消费者不会阻塞 Queue 上的其他消费者,但会积累队列深度;Topic 侧默认有 pendingMessageLimitStrategy 限制慢订阅的滞留量(镜像默认 conf 对 topic> 设了 constant limit=1000),超限丢弃最旧消息保护生产端。

JMSXGroupID:同组粘连 ​

需要「同一订单的消息顺序处理、不同订单并行」时,用消息属性 JMSXGroupID:

  • 同一 group id 的消息被粘连给同一消费者,直到该消费者断开(组被重新分配)。
  • 与 Kafka 分区键、Artemis _AMQ_GROUP_ID、RocketMQ MessageGroup 同类心智,但 Classic 无分区:粘连由 Broker 维护的组归属完成。
  • 组数量远大于消费者数时负载均衡良好;少数巨型组会造成倾斜。

内容过滤:Selector ​

消费者创建时可带 JMS selector(SQL-92 子集),如 region = 'EU' AND amount > 100:

  • 过滤在 Broker 侧执行,不匹配的消息不会投递给该消费者;
  • selector 只作用于消息属性,不能过滤载荷内容;把路由键放进属性是惯例。

VirtualTopics:Topic 上的竞争消费 ​

Topic 的订阅各自收全量,无法水平扩展单个订阅的处理能力。VirtualTopics 把逻辑 Topic 映射到物理队列(官方文档 E1):

  • 生产者发往 VirtualTopic.orders.events;
  • 每个消费组建一个队列 Consumer.<组ID>.VirtualTopic.orders.events,各队列收全量副本,队列内多消费者竞争消费;
  • 等价于「Kafka 多消费组」心智:组间广播、组内竞争。

另有 composite destination(一次发送扇出到多个目的地)等进阶路由,属低频工具,此处不展开。

与统一模型的对照 ​

其它产品机制Classic 等价物
Kafka 分区键JMSXGroupID(组粘连,非分区)
Kafka 消费组(Topic 上)VirtualTopics 的 Consumer..VirtualTopic. 队列
RabbitMQ exchange + bindingQueue/Topic + selector(无 exchange 层)
RocketMQ SQL92 过滤JMS selector(同为 SQL-92 子集,属性级)

边界 ​

  • 无通配订阅树(不像 NATS Subject 或 MQTT topic filter 在路由层做层级通配;MQTT 协议接入时由其映射处理)。
  • 无按载荷的哈希分区:需要并行度 + 顺序时,用多个队列 + 业务侧分派,或接受 JMSXGroupID 粘连的粒度。

以统一实验验证消息系统语义边界