06-消息队列完全指南

23 阅读10分钟

消息队列完全指南

文档类型: 消息队列核心知识
更新时间: 2025-10-28


目录

  1. 消息队列基础
  2. RabbitMQ详解
  3. Kafka详解
  4. RocketMQ详解
  5. 消息队列对比
  6. 应用场景
  7. 最佳实践
  8. 面试高频题

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_keyProducer ───► 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(相同keyConsumer:
└─ 从Queue 0顺序消费 ✅
消息过滤
// Tag过滤
$producer->send([
    'topic' => 'order',
    'tag' => 'create',  // 标签
    'body' => $data,
]);

// 消费者订阅
$consumer->subscribe('order', 'create || pay');  // 只消费create或pay

5. 消息队列对比

5.1 三大MQ对比

维度RabbitMQKafkaRocketMQ
语言ErlangScala/JavaJava
协议AMQP自定义自定义
吞吐量万级百万级十万级
时延微秒级毫秒级毫秒级
可用性高(主从)非常高(分布式)⭐非常高
消息丢失参数优化后很低配置同步刷盘不丢失
功能特性最丰富简单丰富
事务消息
延迟队列
死信队列
顺序消息
消息回溯
管理界面需第三方
社区活跃非常高

5.2 性能对比

MQ单机TPS集群TPS时延持久化
RabbitMQ1万10万微秒级支持
Kafka10万百万级毫秒级追加写
RocketMQ10万百万级毫秒级支持
Redis Stream10万-微秒级支持
ActiveMQ1万10万毫秒级支持

性能测试(单机,普通消息):

RabbitMQ:  1万 TPS
Kafka:     10万 TPS  ← 吞吐量最高
RocketMQ:  10万 TPS

5.3 功能对比

消息类型
功能RabbitMQKafkaRocketMQ
普通消息
事务消息
顺序消息
延迟消息
定时消息
批量消息
消息可靠性
功能RabbitMQKafkaRocketMQ
消息持久化(追加写)⭐
消息确认(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: 三大作用:

  1. 异步处理:提高响应速度
  2. 削峰填谷:保护系统不被打垮
  3. 服务解耦:降低系统耦合度

Q2: RabbitMQ、Kafka、RocketMQ如何选择?

A:

场景推荐MQ理由
可靠性优先RabbitMQ功能丰富、ACK机制完善
高吞吐量Kafka百万级TPS
日志收集Kafka高吞吐、持久化、回溯
事务消息RocketMQ唯一支持
延迟队列RabbitMQTTL+死信队列
顺序消息Kafka/RocketMQ分区保证顺序
中小规模RabbitMQ简单易用
大数据场景Kafka吞吐量最高

Q3: 如何保证消息不丢失?

A:

1. 生产者:
   - 开启确认模式(RabbitMQ)
   - acks=all(Kafka)
   - 同步发送+重试

2. Broker:
   - 消息持久化
   - 主从同步
   - 多副本

3. 消费者:
   - 手动ACK
   - 消费成功后确认
   - 失败重试

Q4: 如何保证消息不重复消费?

A:

幂等性设计:

  1. 唯一ID去重:Redis记录已处理的消息ID
  2. 数据库唯一索引:消息ID设为主键
  3. 业务逻辑幂等:使用乐观锁或状态机

Q5: 如何保证消息顺序?

A:

  1. Kafka

    • 相同key发到同一分区
    • 消费者单线程消费
  2. RabbitMQ

    • 单队列+单消费者
    • 或消费者内部排序
  3. 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. 总结

核心要点

  1. 基本概念:生产者、消费者、消息、队列、主题
  2. 三大MQ:RabbitMQ、Kafka、RocketMQ的特点和选型
  3. 应用场景:异步、削峰、解耦、日志、事务
  4. 可靠性:消息不丢失、不重复、有序性
  5. 最佳实践:幂等性、监控、容量规划

选型建议

优先级排序:

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 → 中等

推荐资源: