消息队列核心概念详解
返回掌握这些核心概念,才能真正用好消息队列。本文系统讲解消息队列中的关键术语和机制。
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 等具体产品的使用方法和最佳实践。