消息接口原理解析 - 第2节
一、消息模型的核心:从“怎么传”到“怎么收”
大家好,我是你们这堂课的主讲人。今天咱们聊聊消息模型的基础。
说实话,我刚开始接触消息系统时,也被一堆术语搞晕过。什么点对点、发布订阅、Broker、Topic……感觉像在学一门新语言。但后来我发现,这些概念其实没那么玄乎。
说白了,消息模型就是解决两个问题:谁发消息?谁收消息? 以及 怎么保证消息能到?
咱们先看一张总览图,把今天要讲的核心概念串起来。
2.1 点对点模型(P2P)
点对点模型,英文叫 Point-to-Point,简称 P2P。这是最朴素的消息模型。
它的逻辑很简单:一条消息,只能被一个消费者消费。 就像你寄一封挂号信,收件人只有一个。别人不能拆你的信。
核心角色
- Producer(生产者):发消息的一方
- Consumer(消费者):收消息的一方
- Queue(队列):消息暂存的地方
我举个例子。你在电商平台下单后,系统会发一条消息到「订单处理队列」。这个队列里可能有多个消费者在等着抢任务。但不管有多少消费者,一条订单消息只会被其中一个消费者处理。为什么?因为订单不能重复处理啊,否则就发两件货了。
关键特征:
- 消息一旦被消费,就从队列中移除
- 消费者之间是竞争关系
- 适合任务分发、负载均衡场景
我的经验: 我在做物流系统时,曾经用 P2P 模型处理包裹分拣任务。每个包裹消息只能被一个分拣员领取。如果用了 Pub/Sub,那一个包裹就会被多个分拣员同时处理,仓库就乱套了。所以选模型前,一定要想清楚业务场景。
2.2 发布订阅模型(Pub/Sub)
发布订阅模型,英文叫 Publish/Subscribe,简称 Pub/Sub。这个模型就灵活多了。
它的核心思想是:一条消息,可以被多个消费者同时消费。 就像你在公众号发一篇文章,所有关注你的人都能看到。每个人看到的都是同一篇文章。
核心角色
- Publisher(发布者):发布消息的一方
- Subscriber(订阅者):订阅消息的一方
- Topic(主题):消息的分类标签
为什么会需要这个模型?你想想看,用户下单后,订单系统要处理,库存系统要扣减,积分系统要增加,通知系统要发短信……如果每个系统都去轮询数据库,效率太低了。用 Pub/Sub 模型,订单系统只管往「订单创建」这个 Topic 发一条消息,其他系统各自订阅,各取所需。
关键特征:
- 消息不会因为被消费而删除(除非过期)
- 消费者之间是独立关系
- 适合广播通知、事件驱动架构
注意: 我曾经踩过一个坑。在 Pub/Sub 模型里,如果消费者挂了,消息可能会丢失。除非你开启了持久化订阅。嗯,这里要注意,不是所有消息系统默认都做持久化。Kafka 默认会保留消息,但 RabbitMQ 的 Pub/Sub 模式如果不做配置,消息发出去就没了。所以生产环境一定要确认好。
2.3 消息队列的核心概念
聊完了两种模型,咱们再深入看看消息队列里的几个核心角色。这些概念你会在所有消息系统中遇到。
2.3.1 Producer(生产者)
Producer 就是发消息的程序。它不关心消息最终被谁消费,只负责把消息扔到 Broker 上。
我个人的习惯是,在 Producer 端一定要做好重试机制和幂等性。为什么?因为网络可能抖动,Broker 可能暂时不可用。如果发消息失败就直接抛异常,那业务就断了。
// 伪代码示例:Producer 发送消息
public void sendMessage(String topic, String message) {
try {
broker.send(topic, message);
} catch (NetworkException e) {
// 重试3次
retry(3, () -> broker.send(topic, message));
}
}
2.3.2 Consumer(消费者)
Consumer 是收消息的程序。它从 Broker 拉取消息,然后处理业务逻辑。
这里有个关键点:消费确认(ACK)。消费者处理完消息后,要告诉 Broker「我处理好了,你可以删了」。如果消费者没发 ACK,Broker 会认为消息没被处理,会重新投递。
避坑指南: 我曾经遇到过一个问题:消费者处理消息时抛了异常,但代码里忘了发 ACK。结果 Broker 反复投递同一条消息,导致业务数据重复。后来我养成了一个习惯:先处理业务,再发 ACK。如果处理失败,就发 NACK,让 Broker 把消息放回队列。
2.3.3 Broker(消息服务器)
Broker 是消息系统的核心。它负责接收消息、存储消息、路由消息。
你可以把 Broker 想象成一个邮局。Producer 是寄信人,Consumer 是收信人,Broker 就是那个分拣信件、暂存信件的邮局。
Broker 要解决几个关键问题:
- 存储:消息放哪里?内存还是磁盘?
- 持久化:Broker 重启后消息还在吗?
- 高可用:一台 Broker 挂了,消息会丢吗?
- 路由:消息该发给哪个 Consumer?
2.3.4 Topic(主题)
Topic 是消息的分类标签。Producer 发消息时指定 Topic,Consumer 订阅时也指定 Topic。只有 Topic 匹配了,消息才能被消费。
举个例子:
| Topic 名称 | 用途 | 谁生产 | 谁消费 |
|---|---|---|---|
| order.created | 订单创建事件 | 订单服务 | 库存服务、积分服务、通知服务 |
| payment.success | 支付成功事件 | 支付服务 | 订单服务、财务服务 |
| log.error | 错误日志 | 所有服务 | 日志收集服务 |
我个人建议,Topic 的命名要规范。比如用「业务.动作」的格式,这样一看就知道是干什么的。别用「topic1」「topic2」这种名字,三个月后你自己都看不懂。
2.4 两种模型的对比
咱们用一张表来对比一下 P2P 和 Pub/Sub:
| 对比维度 | 点对点(P2P) | 发布订阅(Pub/Sub) |
|---|---|---|
| 消息消费方式 | 一条消息只被一个消费者消费 | 一条消息可被多个消费者消费 |
| 消息生命周期 | 消费后删除 | 消费后保留(直到过期) |
| 消费者关系 | 竞争关系 | 独立关系 |
| 典型场景 | 任务分发、负载均衡 | 事件广播、数据同步 |
| 代表产品 | RabbitMQ(队列模式)、ActiveMQ | Kafka、RocketMQ、RabbitMQ(交换器模式) |
总结一下:
选 P2P 还是 Pub/Sub,取决于你的业务场景。
- 如果一条消息只需要被处理一次,用 P2P。比如订单处理、任务调度。
- 如果一条消息需要被多个系统处理,用 Pub/Sub。比如事件通知、日志收集。
当然,很多消息系统同时支持这两种模型。比如 RabbitMQ 通过队列实现 P2P,通过交换器+绑定实现 Pub/Sub。Kafka 则天生就是 Pub/Sub 模型,但通过消费者组也能实现 P2P 的效果。
好了,这一章的内容就到这里。消息模型是理解消息系统的基石,后面的章节我们会在这个基础上,深入探讨消息的可靠性、顺序性、事务消息等高级话题。
可可精品资料库