消息接口原理解析 - 第2节

一、消息模型的核心:从“怎么传”到“怎么收”

大家好,我是你们这堂课的主讲人。今天咱们聊聊消息模型的基础。

说实话,我刚开始接触消息系统时,也被一堆术语搞晕过。什么点对点、发布订阅、Broker、Topic……感觉像在学一门新语言。但后来我发现,这些概念其实没那么玄乎。

说白了,消息模型就是解决两个问题:谁发消息?谁收消息? 以及 怎么保证消息能到?

咱们先看一张总览图,把今天要讲的核心概念串起来。

消息模型核心概念总览 Producer 消息生产者 发送 Broker 消息服务器 存储 + 路由 Topic 管理 Topic P2P 拉取 Consumer 点对点消费 Pub/Sub 推送 Consumer 订阅消费 图例说明 Producer:生产消息,推送到 Broker Broker:消息中转站,负责存储和路由 Consumer(P2P):一条消息只被一个消费者消费 Consumer(Pub/Sub):一条消息可被多个订阅者消费

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 的效果。

好了,这一章的内容就到这里。消息模型是理解消息系统的基石,后面的章节我们会在这个基础上,深入探讨消息的可靠性、顺序性、事务消息等高级话题。

可可精品资料库