1. 分不清这两种模式,消息队列就很容易“不听话”
我见过不少团队,消息队列用了大半年,突然有一天生产环境出了怪事:同一个消息,明明只想让库存服务去处理,结果订单服务和积分服务也各消费了一份;又或者反过来,一个消息推给了一个消费者组,组里好几个节点抢着处理,处理完了还互相都以为对方没处理完,业务数据就重复了。排查到最后,根子往往不在代码逻辑,而在最基础的那个问题上——你到底用的是点对点模式,还是发布/订阅模式。
这个主题看着基础,其实信息量很大。消息队列几乎所有的高级问题,不管是重复消费、消息丢失、消费组设计、延迟队列,还是面试时被追问的“Kafka的消费组和RabbitMQ的Exchange到底怎么理解”,底层都是这两套消息分发模型在起作用。把这篇文章看完,你能得到三样东西:一套能直接讲清楚两种模式差异的话术;一套基于Spring Boot和Redis Stream的落地示例;还有一个应对面试追问的知识点框架。
先说一个容易让人蒙圈的背景。很多人以为“点对点”和“发布/订阅”是两种不同的消息队列软件,或者以为是两种不同的协议,其实都不是。它们是同一套消息中间件内部支持的两种消息路由语义。ActiveMQ有Queue和Topic两种Destination,RabbitMQ用队列加Exchange组合出两种投递行为,Kafka的消费组机制内部同时存在这两种模式,Redis Stream通过消费者组也把两种模式都用上了。所以问题不是“选哪种消息队列”,而是“在你正在用的这个队列里,怎么定义消费者之间的关系”。
1.1 一个看似简单却总被搞混的问题
先问一个特别基础的问题:消息队列里的消息,消费完了之后去哪儿了?不同的人给出的答案完全不一样。有人说消息被消费者拿走之后就没了,有人说消息还在队列里可以重复读,还有人说同一个消息可以同时发给很多个系统。这三种回答,恰好对应了消息队列演进中的不同设计。
如果消息只是被一个消费者领取后移除,这就是典型的点对点模式。如果消息被广播给所有订阅者,每个订阅者各自拥有一份副本,这就是发布/订阅模式。而Kafka这种比较“现代”的设计,则用消费组的概念把这两种模式统一到了一起:同一个消费组内部是竞争关系,一条消息只给组内的一个消费者;不同消费组之间是订阅关系,每个组都能独立读到同一条消息。
在没把这个问题想明白之前,任何代码层面的效率优化都是虚的。因为你可能连“这条消息应该被谁消费”这个最基础的语义都没定义对。
1.2 点对点模式:一条消息只属于一个消费者
点对点模式,英文叫Point-to-Point,缩写P2P。它的核心语义是:生产者把消息发送到一个队列(Queue),队列里的每一条消息只会被一个消费者接收并处理一次。消息一旦被成功消费,通常就从队列中移除了。
怎么理解?可以把它想成一张只有单座的电影票。电影院放一场电影,票卖出去一张,就只有买到票的那个人能进场,其他人不能再拿这张票进去。更准确地说,这个队列就像一条只能一个人通过的窄桥,桥这头的人把东西送过去,桥那头只有一个人能接到。
在这种模式下,生产者和消费者之间没有强耦合关系,两者处理速度可以不一样,队列天然起到削峰填谷的作用。多个消费者之间是竞争关系,叫“竞争消费者”,系统可以通过增加消费者来提升处理速度,也就是常见的水平扩容。ActiveMQ的Queue、RabbitMQ里多个消费者订阅同一个Queue、以及Kafka里同一个消费组内的多个消费者,本质上都是这种模式。
这里的坑在于,很多人以为“点对点”必须是“一对一”,也就是一个生产者只能配一个消费者。实际上,点对点允许有多个生产者、多个消费者,只是每一条具体消息,只会被其中的某一个消费者消费。它强调的是消息的独占性,不是参与方的数量。
1.3 发布/订阅模式:一份消息广播给所有订阅者
发布/订阅模式,英文Publish/Subscribe,缩写Pub/Sub。它的核心语义是:生产者把消息发布到一个主题(Topic)或频道(Channel),所有订阅了这个主题的消费者都能收到同一份消息的副本。
类比一下就很好懂。这就像广播电台。电台发射信号,只要你的收音机调到了对应频率,就能收到同样的节目,收听的人再多也不影响电台本身,电台也不关心谁在听。在系统中,消息发布方根本不需要知道有多少个订阅方存在,订阅方也不需要你单独注册,订阅关系是松散的。
在这种模式下,同一个消息可以被多个不同的业务系统消费,每个系统拿到的消息都是完整的。它天然解决了“一份数据变更需要通知多个下游系统”的问题。比如用户下单后,订单系统要发通知给库存系统、积分系统、数据分析系统,这时发布/订阅模式就非常合适。
但这里同样有陷阱。很多人把RabbitMQ的Topic Exchange当成某种“特殊的发布/订阅”,其实RabbitMQ里一个Queue被多个消费者绑定时,消息在队列内部是按点对点分配的;要实现真正的发布/订阅,得让每个订阅者都有独立队列,再通过Exchange广播把消息复制到各个队列。这个细节在面试中非常容易被问到。
最后用一个表格把两者的核心差异列出来,方便直接对照:
| 对比维度 | 点对点模式 | 发布/订阅模式 |
|---|---|---|
| 核心语义 | 一条消息只被一个消费者消费 | 一条消息广播给所有订阅者 |
| 消费者关系 | 竞争关系,互相争抢消息 | 平等关系,各自独立消费 |
| 消息去留 | 消费后移除(或标记消费) | 每个订阅者各自维护一份消费进度 |
| 生产与消费耦合度 | 低,存储天然起到缓冲作用 | 更低,发布方完全不感知订阅方 |
| 典型场景 | 任务分发、异步处理、削峰填谷 | 事件广播、数据同步、多系统通知 |
| 离线容错 | 消费者暂时离线消息排队等待 | 取决于订阅者是否有持久化存储/消费组机制 |
这个表格看着简单,但读的时候一定要结合你正在用的消息中间件来看,因为不同中间件对这两种模式的实现细节差别很大,也容易踩坑。接下来进入实操层面,把这两种模式放到真实代码里彻底拆开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
