消息队列完全指南
文档类型: 消息队列核心知识
更新时间: 2025-10-28
目录
1. 消息队列基础
1.1 什么是消息队列?
消息队列(Message Queue, MQ):
应用程序之间的异步通信中间件
基本模型:
生产者 → 消息队列 → 消费者
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 生产者 │ ───► │ 消息队列 │ ───► │ 消费者 │
│ Producer │ │ MQ │ │ Consumer │
└──────────┘ └──────────┘ └──────────┘
1.2 为什么需要消息队列?
异步处理
同步调用(慢):
用户注册 → 写数据库(50ms) → 发邮件(2s) → 发短信(1s) → 返回
总耗时: 3.05秒 ❌
异步处理(快):
用户注册 → 写数据库(50ms) → 投递消息队列(5ms) → 返回
↓
消费者异步处理: 发邮件 + 发短信
总耗时: 55ms ✅
// 同步方式
public function register($data)
{
// 1. 保存用户
$user = User::create($data); // 50ms
// 2. 发送邮件
Mail::send($user->email, '欢迎注册'); // 2s
// 3. 发送短信
SMS::send($user->phone, '验证码'); // 1s
return '注册成功'; // 3.05s后返回
}
// 异步方式(使用消息队列)
public function register($data)
{
// 1. 保存用户
$user = User::create($data); // 50ms
// 2. 投递到消息队列
$queue->push(new SendWelcomeEmailJob($user)); // 5ms
$queue->push(new SendSmsJob($user)); // 5ms
return '注册成功'; // 60ms后返回 ✅
}
削峰填谷
场景:秒杀活动
没有MQ:
┌──────────┐
│ 请求 │ 10000 QPS
│ │││ │ ↓
│ │││ │ 数据库扛不住 ❌
└───────↓──┘
使用MQ:
┌──────────┐ ┌──────┐ ┌──────────┐
│ 请求 │ ──► │ MQ │ ──► │ 数据库 │
│ 10000QPS │ │缓冲 │ │ 1000QPS │
└──────────┘ └──────┘ └──────────┘
消息堆积 ✅ 平稳消费 ✅
服务解耦
没有MQ:
┌────────┐ ┌────────┐ ┌────────┐
│ 订单 │────►│ 库存 │────►│ 物流 │
│ 服务 │ │ 服务 │ │ 服务 │
└────────┘ └────────┘ └────────┘
强耦合:任一服务故障,整个链路失败 ❌
使用MQ:
┌────────┐ ┌──────┐ ┌────────┐
│ 订单 │────►│ MQ │◄────│ 库存 │
│ 服务 │ │ │ │ 服务 │
└────────┘ └──┬───┘ └────────┘
│ ┌────────┐
└────────►│ 物流 │
│ 服务 │
└────────┘
解耦:服务独立,互不影响 ✅
1.3 消息队列模型
点对点模型(Queue)
一对一:一条消息只能被一个消费者消费
┌──────────┐
│ Producer │───┐
└──────────┘ │
▼
┌─────────┐
│ Queue │
└─────────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Consumer│ │Consumer│ │Consumer│
│ #1 │ │ #2 │ │ #3 │
└────────┘ └────────┘ └────────┘
消息轮询分配(负载均衡)
发布订阅模型(Topic)
一对多:一条消息可以被多个消费者消费
┌──────────┐
│ Producer │
└──────┬───┘
│
▼
┌─────────┐
│ Topic │
└─────────┘
│
┌───┴───┐
▼ ▼
┌─────┐ ┌─────┐
│Sub 1│ │Sub 2│
└──┬──┘ └──┬──┘
│ │
▼ ▼
┌────┐ ┌────┐
│C1 │ │C2 │
└────┘ └────┘
所有订阅者都收到消息
2. RabbitMQ详解
2.1 RabbitMQ架构
┌─────────────────────────────────────────────┐
│ RabbitMQ Broker │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Exchange │───►│ Queue │───► Consumer│
│ │ 交换机 │ │ 队列 │ │
│ └────▲─────┘ └──────────┘ │
│ │ │
│ ┌────┴──────────────────┐ │
│ │ Binding (绑定) │ │
│ │ routing_key: order.* │ │
│ └───────────────────────┘ │
└─────────────────────────────────────────────┘
▲
│
┌────┴─────┐
│ Producer │
└──────────┘
核心组件:
- Producer:生产者,发送消息
- Exchange:交换机,接收消息并路由
- Queue:队列,存储消息
- Consumer:消费者,处理消息
- Binding:绑定,Exchange和Queue的关系
2.2 Exchange类型
Direct Exchange(直连)
routing_key完全匹配
Producer ───routing_key: "order"───► Exchange
│
┌───────────────────┼───────────────┐
▼ ▼ ▼
Queue (order) Queue (user) Queue (pay)
routing_key: routing_key: routing_key:
"order" "user" "pay"
// 发送消息
$channel->basic_publish(
$message,
'direct_exchange',
'order' // routing_key
);
// 绑定队列
$channel->queue_bind('order_queue', 'direct_exchange', 'order');
Topic Exchange(主题)
routing_key模式匹配(支持通配符)
* : 匹配一个单词
# : 匹配0个或多个单词
Producer ───"order.create"───► Exchange
│
┌───────────────────┼─────────────────┐
▼ ▼ ▼
Queue #1 Queue #2 Queue #3
"order.*" "order.create" "*.create"
✅ 匹配 ✅ 匹配 ✅ 匹配
Producer ───"user.update"───► Exchange
│
┌───────────────────┼─────────────────┐
▼ ▼ ▼
Queue #1 Queue #2 Queue #3
"order.*" "order.create" "*.create"
❌ 不匹配 ❌ 不匹配 ❌ 不匹配
// 绑定队列
$channel->queue_bind('order_queue', 'topic_exchange', 'order.*');
$channel->queue_bind('create_queue', 'topic_exchange', '*.create');
$channel->queue_bind('all_queue', 'topic_exchange', '#');
Fanout Exchange(广播)
广播给所有队列(忽略routing_key)
Producer ───► Exchange
│
┌────────────┼────────────┐
▼ ▼ ▼
Queue #1 Queue #2 Queue #3
所有队列都收到消息
Headers Exchange(头部)
根据消息头部属性匹配
2.3 RabbitMQ高级特性
消息确认(ACK)
// 手动ACK
$channel->basic_consume(
'queue_name',
'',
false, // no_ack = false(需要手动确认)
false,
false,
false,
function ($message) use ($channel) {
try {
// 处理消息
processMessage($message->body);
// 确认消息
$channel->basic_ack($message->delivery_info['delivery_tag']);
} catch (Exception $e) {
// 拒绝消息并重新入队
$channel->basic_nack(
$message->delivery_info['delivery_tag'],
false, // multiple
true // requeue(重新入队)
);
}
}
);
死信队列(DLX)
消息变成死信的情况:
1. 消息被拒绝(basic_reject/basic_nack且requeue=false)
2. 消息过期(TTL)
3. 队列达到最大长度
应用场景:
- 消息重试
- 延迟队列
- 失败消息处理
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 正常队列 │────►│ 消费失败 │────►│ 死信队列 │
│ │ │ (3次) │ │ (DLX) │
└──────────┘ └──────────┘ └──────────┘
// 声明死信交换机
$channel->exchange_declare('dlx_exchange', 'direct', false, true);
$channel->queue_declare('dlx_queue', false, true);
$channel->queue_bind('dlx_queue', 'dlx_exchange', 'dlx');
// 声明正常队列,指定死信交换机
$channel->queue_declare('normal_queue', false, true, false, false, [
'x-dead-letter-exchange' => ['S', 'dlx_exchange'],
'x-dead-letter-routing-key' => ['S', 'dlx'],
]);
延迟队列
实现方式:TTL + 死信队列
消息 → 延迟队列(TTL=10s) → 死信交换机 → 目标队列 → 消费
场景:
- 订单30分钟未支付自动取消
- 定时消息推送
- 延迟任务执行
// 发送延迟消息
$message = new AMQPMessage($data, [
'delivery_mode' => 2, // 持久化
'expiration' => '10000' // 10秒后过期
]);
$channel->basic_publish($message, '', 'delay_queue');
优先级队列
// 声明优先级队列
$channel->queue_declare('priority_queue', false, true, false, false, [
'x-max-priority' => ['I', 10] // 最大优先级10
]);
// 发送消息时指定优先级
$message = new AMQPMessage($data, [
'priority' => 5 // 优先级5
]);
2.4 RabbitMQ在Hyperf中的使用
// config/autoload/amqp.php
return [
'default' => [
'host' => 'localhost',
'port' => 5672,
'user' => 'guest',
'password' => 'guest',
'vhost' => '/',
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
],
],
];
// 定义消息
namespace App\Amqp\Producer;
use Hyperf\Amqp\Annotation\Producer;
use Hyperf\Amqp\Message\ProducerMessage;
#[Producer(exchange: 'order', routingKey: 'order.create')]
class OrderCreatedProducer extends ProducerMessage
{
public function __construct($data)
{
$this->payload = $data;
}
}
// 生产消息
use Hyperf\Amqp\Producer;
class OrderService
{
public function __construct(
private Producer $producer
) {}
public function create($orderData)
{
// 创建订单
$order = Order::create($orderData);
// 发送消息
$message = new OrderCreatedProducer([
'order_id' => $order->id,
'amount' => $order->amount,
]);
$this->producer->produce($message);
return $order;
}
}
// 消费消息
namespace App\Amqp\Consumer;
use Hyperf\Amqp\Annotation\Consumer;
use Hyperf\Amqp\Message\ConsumerMessage;
use Hyperf\Amqp\Result;
#[Consumer(exchange: 'order', routingKey: 'order.create', queue: 'order.create', nums: 1)]
class OrderCreatedConsumer extends ConsumerMessage
{
public function consumeMessage($data, $message): string
{
// 处理订单创建事件
$orderId = $data['order_id'];
// 扣减库存
$this->inventoryService->reduce($orderId);
// 发送通知
$this->notificationService->send($orderId);
return Result::ACK; // 确认消息
}
}
3. Kafka详解
3.1 Kafka架构
┌────────────────────────────────────────────────────┐
│ Kafka Cluster │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐│
│ │ Broker 1 │ │ Broker 2 │ │ Broker 3 ││
│ │ │ │ │ │ ││
│ │ Topic: order│ │ Topic: order│ │ Topic: order││
│ │ Partition 0 │ │ Partition 1 │ │ Partition 2 ││
│ │ (Leader) │ │ (Follower) │ │ (Leader) ││
│ └─────────────┘ └─────────────┘ └─────────────┘│
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ ZooKeeper / KRaft │ │
│ │ - 元数据管理 │ │
│ │ - Leader选举 │ │
│ └─────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
▲ │
│ ▼
┌─────────┐ ┌──────────┐
│Producer │ │ Consumer │
│ Group │ │ Group │
└─────────┘ └──────────┘
核心概念:
- Broker:Kafka服务器节点
- Topic:消息主题(类别)
- Partition:分区,提高并行度
- Replica:副本,保证高可用
- Consumer Group:消费者组,实现负载均衡
3.2 Kafka分区机制
Topic: order (3个分区)
┌─────────────────────────────────────────┐
│ Partition 0 │
│ [msg1][msg4][msg7]... │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Partition 1 │
│ [msg2][msg5][msg8]... │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Partition 2 │
│ [msg3][msg6][msg9]... │
└─────────────────────────────────────────┘
优势:
✅ 并行处理(3个分区=3个消费者并行)
✅ 顺序保证(同一分区内有序)
✅ 水平扩展(增加分区提高吞吐量)
分区策略:
// 1. 轮询分区(默认)
$producer->send($topic, $message);
// 2. Key哈希分区(保证相同key进入同一分区)
$producer->send($topic, $message, $key);
// 相同订单ID的消息进入同一分区,保证顺序
// 3. 自定义分区
$partitioner = function ($topic, $partitionsNum, $key, $message) {
// 根据用户ID分区
$userId = $message['user_id'];
return $userId % $partitionsNum;
};
3.3 Kafka消费者组
Consumer Group: 消费者组内的消费者共同消费Topic
Topic: order (3个分区)
Consumer Group 1:
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Partition │ │ Partition │ │ Partition │
│ 0 │ │ 1 │ │ 2 │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Consumer 1│ │Consumer 2│ │Consumer 3│
└──────────┘ └──────────┘ └──────────┘
规则:
- 一个分区只能被组内一个消费者消费
- 一个消费者可以消费多个分区
- 消费者数量 > 分区数:有消费者空闲
- 消费者数量 < 分区数:一个消费者消费多个分区
3.4 Kafka保证可靠性
生产者确认(acks)
// acks = 0:不等待确认(最快,可能丢失)
$producer->send($topic, $message, ['acks' => 0]);
// acks = 1:等待Leader确认(默认)
$producer->send($topic, $message, ['acks' => 1]);
// acks = all:等待所有副本确认(最可靠,最慢)
$producer->send($topic, $message, ['acks' => 'all']);
消费者位移管理
Offset: 消费位置
Partition 0:
[msg0][msg1][msg2][msg3][msg4][msg5][msg6]...
↑
Consumer Offset = 2(下次从msg3开始消费)
位移提交策略:
1. 自动提交:定期提交(可能重复消费)
2. 手动提交:消费成功后提交(更可靠)
// 手动提交offset
$consumer->consume(function ($message) {
try {
// 处理消息
processMessage($message);
// 手动提交offset
$consumer->commit($message);
} catch (Exception $e) {
// 不提交offset,下次重新消费
logger()->error('消费失败', ['error' => $e->getMessage()]);
}
});
3.5 Kafka在Hyperf中的使用
// config/autoload/kafka.php
return [
'default' => [
'bootstrap_servers' => 'localhost:9092',
'group_id' => 'my-group',
'auto_offset_reset' => 'earliest',
'enable_auto_commit' => false, // 手动提交
],
];
// 生产者
use longlang\phpkafka\Producer\Producer;
$producer = new Producer([
'bootstrap_servers' => 'localhost:9092',
'acks' => 'all',
]);
$producer->send('order-topic', json_encode([
'order_id' => 123,
'amount' => 100,
]));
// 消费者
use longlang\phpkafka\Consumer\Consumer;
$consumer = new Consumer([
'bootstrap_servers' => 'localhost:9092',
'group_id' => 'order-group',
'topics' => ['order-topic'],
'enable_auto_commit' => false,
]);
$consumer->consume(function ($message) use ($consumer) {
$data = json_decode($message->getValue(), true);
// 处理订单
processOrder($data['order_id']);
// 提交offset
$consumer->commit($message);
});
4. RocketMQ详解
4.1 RocketMQ架构
┌─────────────────────────────────────────────────┐
│ RocketMQ 集群 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ NameSrv │ │ NameSrv │ │ NameSrv │ │
│ │ 节点1 │ │ 节点2 │ │ 节点3 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └─────────────┴─────────────┘ │
│ │ │
│ ┌─────────────┴─────────────┐ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Broker │ │ Broker │ │
│ │ Master │◄────────────►│ Slave │ │
│ │ │ 主从同步 │ │ │
│ │ Topic A │ │ Topic A │ │
│ │ Queue 0-3│ │ Queue 0-3│ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────┘
▲ │
│ ▼
┌─────────┐ ┌──────────┐
│Producer │ │ Consumer │
└─────────┘ └──────────┘
4.2 RocketMQ核心特性
事务消息
RocketMQ事务消息流程:
1. 发送半消息(Half Message)
2. 执行本地事务
3. 提交/回滚事务消息
4. 如果超时未确认,Broker回查本地事务状态
┌──────────┐ ┌──────────┐
│ Producer │ │ Broker │
└─────┬────┘ └────┬─────┘
│ │
├─ 1. 发送半消息 ──────────────►│
│ │ 存储,消费者不可见
│◄─ 2. 半消息发送成功 ──────────┤
│ │
├─ 3. 执行本地事务 │
│ (成功/失败) │
│ │
├─ 4. 提交/回滚 ──────────────►│
│ │ 消息可见/删除
│ │
│ │
│ (超时未确认) │
│◄─ 5. 回查事务状态 ────────────┤
│ │
├─ 6. 返回事务状态 ────────────►│
// RocketMQ事务消息(PHP实现概念)
use MQ\MQProducer;
$producer = new MQProducer([
'namesrv' => 'localhost:9876',
]);
// 发送事务消息
$producer->sendMessageInTransaction(
topic: 'order-topic',
tag: 'create',
body: json_encode($orderData),
executeLocalTransaction: function ($message) {
// 执行本地事务
return Db::transaction(function () use ($message) {
$order = Order::create($message);
return TransactionStatus::COMMIT;
});
},
checkLocalTransaction: function ($message) {
// 回查本地事务状态
$order = Order::find($message['order_id']);
return $order ? TransactionStatus::COMMIT : TransactionStatus::ROLLBACK;
}
);
顺序消息
场景:订单状态变更必须按顺序
订单123: 创建 → 支付 → 发货 → 完成
实现:相同订单ID的消息进入同一队列
Producer:
├─ 消息1: 订单123创建 → Queue 0
├─ 消息2: 订单123支付 → Queue 0(相同key)
├─ 消息3: 订单123发货 → Queue 0(相同key)
└─ 消息4: 订单123完成 → Queue 0(相同key)
Consumer:
└─ 从Queue 0顺序消费 ✅
消息过滤
// Tag过滤
$producer->send([
'topic' => 'order',
'tag' => 'create', // 标签
'body' => $data,
]);
// 消费者订阅
$consumer->subscribe('order', 'create || pay'); // 只消费create或pay
5. 消息队列对比
5.1 三大MQ对比
| 维度 | RabbitMQ | Kafka | RocketMQ |
|---|---|---|---|
| 语言 | Erlang | Scala/Java | Java |
| 协议 | AMQP | 自定义 | 自定义 |
| 吞吐量 | 万级 | 百万级 ⭐ | 十万级 |
| 时延 | 微秒级 ⭐ | 毫秒级 | 毫秒级 |
| 可用性 | 高(主从) | 非常高(分布式)⭐ | 非常高 |
| 消息丢失 | 低 | 参数优化后很低 | 配置同步刷盘不丢失 |
| 功能特性 | 最丰富 ⭐ | 简单 | 丰富 |
| 事务消息 | ❌ | ❌ | ✅ ⭐ |
| 延迟队列 | ✅ ⭐ | ❌ | ✅ ⭐ |
| 死信队列 | ✅ ⭐ | ❌ | ✅ |
| 顺序消息 | ✅ | ✅ ⭐ | ✅ ⭐ |
| 消息回溯 | ❌ | ✅ ⭐ | ✅ |
| 管理界面 | ✅ ⭐ | 需第三方 | ✅ |
| 社区活跃 | 高 | 非常高 ⭐ | 高 |
5.2 性能对比
| MQ | 单机TPS | 集群TPS | 时延 | 持久化 |
|---|---|---|---|---|
| RabbitMQ | 1万 | 10万 | 微秒级 | 支持 |
| Kafka | 10万 | 百万级 ⭐ | 毫秒级 | 追加写 ⭐ |
| RocketMQ | 10万 | 百万级 | 毫秒级 | 支持 |
| Redis Stream | 10万 | - | 微秒级 | 支持 |
| ActiveMQ | 1万 | 10万 | 毫秒级 | 支持 |
性能测试(单机,普通消息):
RabbitMQ: 1万 TPS
Kafka: 10万 TPS ← 吞吐量最高
RocketMQ: 10万 TPS
5.3 功能对比
消息类型
| 功能 | RabbitMQ | Kafka | RocketMQ |
|---|---|---|---|
| 普通消息 | ✅ | ✅ | ✅ |
| 事务消息 | ❌ | ❌ | ✅ ⭐ |
| 顺序消息 | ✅ | ✅ | ✅ ⭐ |
| 延迟消息 | ✅ ⭐ | ❌ | ✅ ⭐ |
| 定时消息 | ❌ | ❌ | ✅ |
| 批量消息 | ✅ | ✅ ⭐ | ✅ |
消息可靠性
| 功能 | RabbitMQ | Kafka | RocketMQ |
|---|---|---|---|
| 消息持久化 | ✅ | ✅(追加写)⭐ | ✅ |
| 消息确认 | ✅(ACK)⭐ | ✅(Offset) | ✅ |
| 死信队列 | ✅ ⭐ | ❌ | ✅ |
| 消息重试 | ✅ ⭐ | 手动 | ✅ |
| 消息回溯 | ❌ | ✅ ⭐ | ✅ |
| 消息过滤 | ✅(灵活)⭐ | ✅(简单) | ✅ |
6. 应用场景
6.1 场景选型
┌─────────────────────────────────────────────────┐
│ 消息队列选型指南 │
└─────────────────────────────────────────────────┘
RabbitMQ 适用场景:
✅ 可靠性要求高(金融、支付)
✅ 需要复杂路由(多种Exchange)
✅ 需要延迟队列
✅ 需要死信队列
✅ 中小规模(TPS < 10万)
✅ 管理界面友好
Kafka 适用场景:
✅ 大数据量(日志、用户行为)
✅ 高吞吐量(TPS > 10万)
✅ 数据流处理
✅ 消息回溯
✅ 实时计算(Kafka Streams)
✅ 日志收集(ELK Stack)
RocketMQ 适用场景:
✅ 电商场景(阿里出品)
✅ 事务消息(分布式事务)
✅ 顺序消息
✅ 定时消息
✅ 高可用要求
✅ 中文文档友好
6.2 实际应用场景
1. 异步处理(所有MQ)
场景:用户注册
同步方式(慢):
注册 → 写DB(50ms) → 发邮件(2s) → 发短信(1s) → 返回
总计: 3.05秒
异步方式(快):
注册 → 写DB(50ms) → 发MQ(5ms) → 返回
↓
MQ消费者异步处理
总计: 55ms
推荐:RabbitMQ(简单可靠)
2. 削峰填谷(所有MQ)
场景:秒杀活动
高峰期: 10000 QPS ──► MQ缓冲 ──► 数据库1000 QPS
低峰期: 100 QPS ──► MQ缓冲 ──► 数据库1000 QPS
效果:
✅ 保护数据库不被打垮
✅ 流量平滑
✅ 系统稳定
推荐:Kafka(高吞吐)或RocketMQ
3. 服务解耦(所有MQ)
场景:订单创建
紧耦合:
订单服务 → 库存服务 → 物流服务 → 积分服务
任一服务故障,整个流程失败 ❌
解耦:
订单服务 → MQ → 库存服务(订阅)
→ 物流服务(订阅)
→ 积分服务(订阅)
服务独立,互不影响 ✅
推荐:RabbitMQ(灵活路由)
4. 日志收集(Kafka)
场景:ELK Stack
应用服务器1 ──┐
应用服务器2 ──┤
应用服务器3 ──┼──► Kafka ──► Logstash ──► ElasticSearch ──► Kibana
应用服务器4 ──┤
应用服务器5 ──┘
优势:
✅ 高吞吐(百万级)
✅ 数据不丢失
✅ 消息回溯
必选:Kafka ⭐
5. 用户行为追踪(Kafka)
场景:用户行为分析
用户操作 → 埋点 → Kafka → Spark Streaming → 实时分析
→ Flink → 实时推荐
→ HDFS → 离线分析
数据量:
- 日均PV: 1亿+
- 数据量: TB级
必选:Kafka ⭐
6. 分布式事务(RocketMQ)
场景:下单扣库存
┌─────────────┐ ┌─────────────┐
│ 订单服务 │ │ 库存服务 │
└──────┬──────┘ └──────┬──────┘
│ │
├─ 1. 发送半消息 ─────────────────►│
│ │
├─ 2. 创建订单(本地事务) │
│ │
├─ 3. 提交事务消息 ───────────────►│
│ │ 消息可见
│ │
│ ├─ 4. 消费消息
│ │
│ ├─ 5. 扣减库存
│ │
│◄─────────── ACK ─────────────────┤
必选:RocketMQ ⭐(唯一支持事务消息)
7. 延迟任务(RabbitMQ/RocketMQ)
场景:订单超时取消
创建订单 → MQ(延迟30分钟) → 30分钟后消费 → 检查订单 → 未支付则取消
RabbitMQ实现:
- TTL + 死信队列
RocketMQ实现:
- 延迟级别(18个级别)
推荐:RabbitMQ(灵活)或RocketMQ(简单)
8. 顺序消息(Kafka/RocketMQ)
场景:订单状态流转
订单123:
├─ 消息1:创建(Partition 0)
├─ 消息2:支付(Partition 0)← 相同分区保证顺序
├─ 消息3:发货(Partition 0)
└─ 消息4:完成(Partition 0)
Kafka:通过分区保证顺序
RocketMQ:MessageQueue保证顺序
推荐:Kafka或RocketMQ
7. 最佳实践
7.1 消息幂等性
/**
* 问题:消息可能重复消费
* 原因:网络抖动、消费者崩溃等
*/
// 方案1:唯一ID去重
class MessageHandler
{
public function handle($message)
{
$messageId = $message['id'];
// 检查是否已处理
if (Redis::exists("processed:{$messageId}")) {
return; // 已处理,跳过
}
// 处理消息
$this->process($message);
// 标记已处理
Redis::setex("processed:{$messageId}", 86400, 1);
}
}
// 方案2:数据库唯一索引
CREATE TABLE order_messages (
message_id VARCHAR(100) PRIMARY KEY, -- 唯一索引
order_id BIGINT,
created_at DATETIME
);
// 插入时会因唯一索引冲突而失败
try {
DB::table('order_messages')->insert([
'message_id' => $messageId,
'order_id' => $orderId,
]);
// 处理业务
$this->process($message);
} catch (DuplicateKeyException $e) {
// 重复消息,忽略
}
// 方案3:业务逻辑幂等
// 使用乐观锁或状态机保证幂等
$affected = DB::table('orders')
->where('id', $orderId)
->where('status', 'pending')
->update(['status' => 'paid']);
if ($affected === 0) {
// 已经处理过了
}
7.2 消息可靠性
/**
* 确保消息不丢失
*/
// 1. 生产者确认
// RabbitMQ
$channel->confirm_select(); // 开启确认模式
$channel->basic_publish($message, $exchange, $routingKey);
$channel->wait_for_pending_acks(); // 等待确认
// Kafka
$producer->send($topic, $message, ['acks' => 'all']);
// 2. 消息持久化
// RabbitMQ
$message = new AMQPMessage($data, [
'delivery_mode' => 2 // 持久化
]);
$channel->queue_declare('queue', false, true); // 队列持久化
// 3. 消费者手动ACK
$channel->basic_consume('queue', '', false, false, false, false, function ($message) {
try {
processMessage($message);
$message->ack(); // 处理成功后确认
} catch (Exception $e) {
$message->nack(true); // 失败重新入队
}
});
7.3 消息顺序性
/**
* 保证消息顺序
*/
// Kafka:相同key进入同一分区
$producer->send('order-topic', $message, $orderId); // 订单ID作为key
// RocketMQ:相同key进入同一队列
$producer->send([
'topic' => 'order',
'body' => $data,
'shardingKey' => $orderId, // 分片key
]);
// 消费者:单线程顺序消费
// 或使用分布式锁确保同一订单的消息串行处理
7.4 消息积压处理
/**
* 消息积压原因:
* - 消费速度 < 生产速度
* - 消费者故障
* - 业务逻辑耗时
*/
// 方案1:增加消费者
// Hyperf配置
#[Consumer(nums: 5)] // 启动5个消费者进程
// 方案2:批量消费
$messages = $consumer->consume(100); // 一次消费100条
foreach ($messages as $message) {
// 批量处理
}
DB::insert($data); // 批量插入数据库
// 方案3:优化消费逻辑
// ❌ 慢:每条消息都查询数据库
foreach ($messages as $message) {
$user = User::find($message['user_id']); // N次查询
}
// ✅ 快:批量查询
$userIds = array_column($messages, 'user_id');
$users = User::whereIn('id', $userIds)->get()->keyBy('id');
foreach ($messages as $message) {
$user = $users[$message['user_id']]; // 内存查找
}
// 方案4:临时扩容
// 增加分区/队列数量
// 增加消费者数量
8. 面试高频题
Q1: 消息队列有什么用?
A: 三大作用:
- 异步处理:提高响应速度
- 削峰填谷:保护系统不被打垮
- 服务解耦:降低系统耦合度
Q2: RabbitMQ、Kafka、RocketMQ如何选择?
A:
| 场景 | 推荐MQ | 理由 |
|---|---|---|
| 可靠性优先 | RabbitMQ | 功能丰富、ACK机制完善 |
| 高吞吐量 | Kafka | 百万级TPS |
| 日志收集 | Kafka | 高吞吐、持久化、回溯 |
| 事务消息 | RocketMQ | 唯一支持 |
| 延迟队列 | RabbitMQ | TTL+死信队列 |
| 顺序消息 | Kafka/RocketMQ | 分区保证顺序 |
| 中小规模 | RabbitMQ | 简单易用 |
| 大数据场景 | Kafka | 吞吐量最高 |
Q3: 如何保证消息不丢失?
A:
1. 生产者:
- 开启确认模式(RabbitMQ)
- acks=all(Kafka)
- 同步发送+重试
2. Broker:
- 消息持久化
- 主从同步
- 多副本
3. 消费者:
- 手动ACK
- 消费成功后确认
- 失败重试
Q4: 如何保证消息不重复消费?
A:
幂等性设计:
- 唯一ID去重:Redis记录已处理的消息ID
- 数据库唯一索引:消息ID设为主键
- 业务逻辑幂等:使用乐观锁或状态机
Q5: 如何保证消息顺序?
A:
-
Kafka:
- 相同key发到同一分区
- 消费者单线程消费
-
RabbitMQ:
- 单队列+单消费者
- 或消费者内部排序
-
RocketMQ:
- MessageQueue顺序
- 顺序消费模式
Q6: 消息积压如何处理?
A:
排查原因:
1. 消费速度慢 → 优化消费逻辑
2. 消费者故障 → 重启消费者
3. 消费者不足 → 增加消费者
解决方案:
1. 增加消费者数量
2. 批量消费
3. 优化消费逻辑
4. 临时扩容
Q7: Kafka为什么这么快?
A:
1. 顺序写磁盘:
- 顺序写性能接近内存
- 不需要磁盘寻道
2. Zero Copy(零拷贝):
- 减少数据拷贝次数
- sendfile系统调用
3. 批量发送:
- 批量压缩
- 减少网络开销
4. 分区并行:
- 多分区并行读写
- 水平扩展
5. PageCache:
- 利用操作系统缓存
- 减少磁盘IO
9. 实战案例
9.1 电商订单系统
// 订单创建流程
// 1. 订单服务:创建订单
class OrderService
{
public function create($orderData)
{
// 创建订单
$order = DB::transaction(function () use ($orderData) {
$order = Order::create($orderData);
// 发送消息
$this->rabbitMQ->publish('order.created', [
'order_id' => $order->id,
'user_id' => $order->user_id,
'product_id' => $order->product_id,
'quantity' => $order->quantity,
]);
return $order;
});
return $order;
}
}
// 2. 库存服务:监听订单创建事件
#[Consumer(exchange: 'order', routingKey: 'order.created')]
class OrderCreatedConsumer
{
public function consume($data): string
{
// 扣减库存
$this->inventoryService->reduce(
$data['product_id'],
$data['quantity']
);
return Result::ACK;
}
}
// 3. 积分服务:监听订单创建事件
#[Consumer(exchange: 'order', routingKey: 'order.created')]
class OrderCreatedPointConsumer
{
public function consume($data): string
{
// 增加积分
$this->pointService->add(
$data['user_id'],
$data['amount'] * 0.01
);
return Result::ACK;
}
}
// 4. 通知服务:监听订单创建事件
#[Consumer(exchange: 'order', routingKey: 'order.created')]
class OrderCreatedNotifyConsumer
{
public function consume($data): string
{
// 发送通知
$this->notifyService->send($data['user_id'], '订单创建成功');
return Result::ACK;
}
}
9.2 日志收集系统
// 使用Kafka收集应用日志
// 生产者:应用日志
class Logger
{
private $kafka;
public function log($level, $message, $context = [])
{
// 发送到Kafka
$this->kafka->send('app-logs', json_encode([
'level' => $level,
'message' => $message,
'context' => $context,
'timestamp' => time(),
'host' => gethostname(),
]));
}
}
// 消费者:Logstash消费Kafka写入ES
// Logstash配置
input {
kafka {
bootstrap_servers => "localhost:9092"
topics => ["app-logs"]
group_id => "logstash"
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
}
9.3 分布式事务
// 使用RocketMQ实现分布式事务
class OrderService
{
public function createOrderWithTransaction($orderData)
{
// 发送事务消息
$this->rocketmq->sendInTransaction(
topic: 'order-topic',
tag: 'create',
body: $orderData,
// 本地事务
executeLocalTransaction: function ($message) {
return DB::transaction(function () use ($message) {
// 创建订单
$order = Order::create($message);
if ($order) {
return TransactionStatus::COMMIT;
}
return TransactionStatus::ROLLBACK;
});
},
// 事务回查
checkLocalTransaction: function ($message) {
$orderId = $message['order_id'];
$order = Order::find($orderId);
return $order
? TransactionStatus::COMMIT
: TransactionStatus::ROLLBACK;
}
);
}
}
10. 总结
核心要点
- 基本概念:生产者、消费者、消息、队列、主题
- 三大MQ:RabbitMQ、Kafka、RocketMQ的特点和选型
- 应用场景:异步、削峰、解耦、日志、事务
- 可靠性:消息不丢失、不重复、有序性
- 最佳实践:幂等性、监控、容量规划
选型建议
优先级排序:
1. 看吞吐量:
- < 1万 → 任意MQ
- 1-10万 → RabbitMQ/RocketMQ
- > 10万 → Kafka
2. 看功能需求:
- 事务消息 → RocketMQ(唯一)
- 延迟队列 → RabbitMQ/RocketMQ
- 日志收集 → Kafka
- 顺序消息 → Kafka/RocketMQ
3. 看团队熟悉度:
- 熟悉Java → Kafka/RocketMQ
- 熟悉Erlang → RabbitMQ
- 都不熟悉 → RabbitMQ(最简单)
4. 看运维成本:
- RabbitMQ → 简单
- Kafka → 依赖ZooKeeper(复杂)
- RocketMQ → 中等
推荐资源:
- RabbitMQ官方文档: www.rabbitmq.com/
- Kafka官方文档: kafka.apache.org/
- RocketMQ官方文档: rocketmq.apache.org/
- Hyperf AMQP文档: hyperf.wiki/3.1/#/zh-cn…