消息队列核心概念详解

返回

掌握这些核心概念,才能真正用好消息队列。本文系统讲解消息队列中的关键术语和机制。

1. 角色与基本对象

消息队列中有三个基本角色:

┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│  Producer   │ ──→  │   Broker    │ ──→  │  Consumer   │
│  (生产者)   │      │  (服务器)   │      │  (消费者)   │
└─────────────┘      └─────────────┘      └─────────────┘
角色说明示例
Producer(生产者)发送消息的一方下单服务发送”订单已创建”消息
Consumer(消费者)处理消息的一方库存服务消费消息后扣减库存
Broker(消息中间件/服务器)存储和转发消息的服务器RabbitMQ、Kafka、RocketMQ 的服务端
Message(消息)传输的数据本体通常包含 key/body/headers

消息结构示例

{
  "key": "order_12345",
  "body": {
    "orderId": "12345",
    "userId": "user_001",
    "amount": 299.00
  },
  "headers": {
    "contentType": "application/json",
    "timestamp": 1709625600000
  }
}

2. 队列与主题

消息队列有两种基本的消息组织方式:

Queue(队列,点对点模式)

Producer → [Queue] → Consumer-A  ✅ 收到消息
                    → Consumer-B  ❌ 收不到(同一条消息只被一个消费者处理)
  • 特点:同一条消息只会被一个消费者处理
  • 消费方式:同一消费者组内竞争消费
  • 适用场景:任务分发、负载均衡

Topic(主题,发布订阅模式)

Producer → [Topic] → Subscriber-A  ✅ 收到消息
                    → Subscriber-B  ✅ 收到消息(各消费一份)
                    → Subscriber-C  ✅ 收到消息
  • 特点:消息可以被多个订阅者各自消费一份
  • 消费方式:广播/多方处理
  • 适用场景:事件通知、数据同步

💡 很多系统同时支持 Queue 和 Topic,只是叫法或实现略有不同。

  • RabbitMQ:Exchange + Queue
  • Kafka:Topic + Partition
  • RocketMQ:Topic + Queue

3. 消费组织方式

Consumer Group(消费者组)

一组消费者实例共同消费同一类消息:

                ┌─ Consumer-1 ──┐
[Topic/Queue] → │─ Consumer-2 ──│  ← 同一个消费者组
                └─ Consumer-3 ──┘

消费规则:

范围规则
组内负载均衡(一条消息只给组内一个实例)
组间互不影响(不同组都能各消费一份)

示例:

订单 Topic
├─ 库存消费者组(3 个实例)→ 每个订单消息只被其中一个处理
├─ 短信消费者组(2 个实例)→ 每个订单消息也只被其中一个处理
└─ 日志消费者组(1 个实例)→ 每个订单消息都被处理

Subscription(订阅)

消费者对某个 Topic 订阅了才会收到消息。未订阅的 Topic 发送的消息,消费者不会收到。


4. 消息确认与投递语义

ACK(确认机制)

Consumer 收到消息 → 处理业务 → 发送 ACK → Broker 删除消息

                    处理失败 → 发送 NACK → Broker 重试或转死信
类型说明
ACK确认,消费者处理成功后告诉 Broker:“这条我处理完了”
NACK/Reject拒绝/失败,告诉 Broker:“这条不行”,后续可能重试或进入死信

投递语义(Delivery Semantics)

语义说明特点
At most once最多一次可能丢失、不重复
At least once至少一次不太会丢、可能重复 ⭐ 最常见
Exactly once恰好一次不丢不重复,最难实现、条件多

⚠️ “至少一次”是最常见的默认语义,所以幂等性几乎是必做的


5. 幂等与去重(非常关键)

什么是幂等?

Idempotency(幂等):同一条消息即使被消费多次,结果也一样。

为什么需要幂等?

因为”至少一次”投递语义很常见,网络抖动、消费者重启等情况都可能导致消息重复投递。

常见做法

方法说明示例
业务唯一 ID 去重表用订单号/消息 ID 做唯一索引INSERT IGNORE INTO processed_orders (order_id) VALUES (?)
状态机检查当前状态,只允许特定状态转换订单状态:待支付 → 已支付(不能重复支付)
唯一索引数据库层面保证唯一性UNIQUE KEY uk_order (order_id)

代码示例

// 伪代码:幂等处理订单消息
public void handleOrderMessage(OrderMessage msg) {
    String orderId = msg.getOrderId();
    
    // 1. 检查是否已处理
    if (orderService.isProcessed(orderId)) {
        log.info("订单已处理,跳过:{}", orderId);
        return;
    }
    
    // 2. 处理业务(带唯一索引,重复会抛异常)
    try {
        orderService.process(orderId);
    } catch (DuplicateKeyException e) {
        log.info("重复消息,忽略:{}", orderId);
    }
}

6. 重试、死信、延迟

Retry(重试)

消费失败后的自动重试机制:

消费失败 → 等待 1 秒 → 重试 1 次
        → 等待 5 秒 → 重试 2 次
        → 等待 30 秒 → 重试 3 次
        → 超过最大重试次数 → 转入死信队列

DLQ / Dead Letter Queue(死信队列)

多次失败/过期/被拒绝的消息会进入死信队列:

正常队列 → [消息处理失败 3 次] → 死信队列 → 人工排查/补偿处理

死信来源:

  • 消费失败超过最大重试次数
  • 消息过期(TTL 到期)
  • 队列满了被拒绝
  • 消费者主动拒绝(NACK)

Delay / TTL(延迟/过期时间)

概念说明示例
TTL(Time To Live)消息多长时间没被处理就过期设置 24 小时,超时自动删除
Delay(延迟投递)到时间才投递给消费者”15 分钟未支付取消订单”

延迟队列应用场景:

  • 订单超时取消
  • 预约任务定时执行
  • 重试间隔控制

7. 顺序与并发

Ordering(顺序消息)

某些业务要求消息按顺序处理:

正确顺序:订单创建 → 订单支付 → 订单发货 → 订单签收
错误顺序:订单发货 → 订单创建 → ... ❌

常见策略:

策略说明适用场景
按 key 分区相同 key 的消息发送到同一分区同一订单的操作
同一队列所有消息走同一个队列全局顺序要求
单消费者一个队列只有一个消费者简单场景

Concurrency(并发消费)

多个消费者实例并行处理,提高吞吐量:

[Queue] → Consumer-1 ──┐
        → Consumer-2 ──┼── 并行处理,提高吞吐
        → Consumer-3 ──┘

⚠️ 并发会让顺序更难保证,需要根据业务权衡。


8. 消费位点/偏移量(Kafka 类特别重要)

Offset(偏移量/位点)

消费者消费到哪一条了,像”读到第几行日志”:

Topic: [msg-0][msg-1][msg-2][msg-3][msg-4][msg-5]

                      offset=3 (下一条从 msg-3 开始)

Commit(提交位点)

确认”我已经处理到这里”,用于故障恢复/重启续读:

消费 msg-0 → 消费 msg-1 → 消费 msg-2 → commit offset=3

                                    重启后从 msg-3 继续

Replay(回放)

把位点回退,重新消费历史消息:

当前 offset=1000 → 重置 offset=500 → 重新消费 500-1000 的消息

应用场景:

  • 代码 bug 修复后补数
  • 数据修复/重算
  • 新服务上线消费历史数据

9. 积压与限流

Backlog / Lag(堆积/延迟)

队列里消息越来越多,消费跟不上:

生产速度:1000 条/秒
消费速度:200 条/秒

积压速度:800 条/秒 → 1 小时积压 288 万条!

告警指标:

  • 队列长度超过阈值
  • 消费延迟(Lag)超过阈值
  • 消费者处理时间变长

Flow Control(流控)

限制生产/消费速率,避免打爆系统:

方式说明
生产者限流超过速率限制后拒绝或降级
消费者限流控制拉取速度,避免处理不过来
队列配额设置队列最大长度,满了拒绝写入

总结

概念核心要点
角色Producer、Consumer、Broker
队列 vs 主题点对点 vs 发布订阅
消费者组组内负载均衡,组间互不影响
ACK确认机制,保证可靠性
投递语义至少一次最常见,需要幂等
幂等必做!用唯一 ID 去重
重试/死信失败处理和兜底
顺序按 key 分区保证局部有序
偏移量消费位点,支持回放
积压监控 + 限流 + 扩容

📚 下一篇: 深入讲解 RabbitMQ、Kafka、RocketMQ 等具体产品的使用方法和最佳实践。