RabbitMQ 实战指南:消息模型、安装与 ThinkPHP 示例

最后更新:2026-07-29
文章目录 23 个章节

引言

RabbitMQ 是一个开源的消息代理软件(消息队列),实现了高级消息队列协议 (AMQP)。它常用于分布式系统中处理异步通信、任务队列和系统解耦。本文将指导您安装 RabbitMQ,并提供 ThinkPHP 中的简单示例,帮助您快速上手。

目录

基础概念

RabbitMQ 是基于 AMQP(高级消息队列协议)的消息代理。核心组件包括:

  • 生产者 (Producer): 发送消息的应用程序。
  • 消费者 (Consumer): 接收并处理消息的应用程序。
  • 队列 (Queue): 存储消息的缓冲区。消息在队列中等待被消费。
  • 交换机 (Exchange): 接收生产者消息并根据规则路由到队列。有四种类型:Direct、Fanout、Topic 和 Headers。
  • 绑定 (Binding): 交换机与队列的关联规则。
  • 虚拟主机 (Virtual Host): 逻辑分组,用于多租户隔离。

这些组件确保消息可靠传递,支持异步通信和系统解耦。

工作模式

RabbitMQ 支持多种工作模式,适用于不同场景:

  1. 简单模式 (Simple): 一个生产者直接发送消息到一个队列,一个消费者消费。适合基本任务。
  2. 工作队列 (Work Queues): 消息分发给多个消费者,实现负载均衡。消费者竞争消费消息。
  3. 发布/订阅 (Publish/Subscribe): 通过 Fanout 交换机广播消息到所有绑定队列。适合日志系统。
  4. 路由 (Routing): 使用 Direct 交换机,根据路由键选择性路由消息。
  5. 主题 (Topics): 使用 Topic 交换机,根据模式匹配路由键(如 "log.*")路由消息。
  6. RPC (Remote Procedure Call): 实现同步调用,客户端等待响应。

选择模式取决于应用需求,如广播 vs. 选择性路由。

高级主题

  • 消息确认 (Acknowledgment): 消费者确认消息处理成功,防止丢失。支持手动/自动确认。
  • 持久化 (Persistence): 队列和消息标记为持久化,重启后保留。
  • 死信队列 (Dead Letter Queues): 处理无法消费的消息(如过期或拒绝)。
  • TTL (Time-To-Live): 为消息或队列设置过期时间。
  • 优先级队列: 消息带有优先级,高优先级先消费。

这些功能提升系统的可靠性和灵活性。

安装

Windows 安装

RabbitMQ 依赖 Erlang/OTP,二者版本必须匹配。先查 Erlang 兼容矩阵,再按 Windows 官方安装指南 安装 64 位 Erlang 和 RabbitMQ。

安装后在 RabbitMQ 的 sbin 目录执行:

rabbitmq-diagnostics.bat status
rabbitmq-plugins.bat enable rabbitmq_management

Debian / Ubuntu 安装

系统仓库中的 RabbitMQ 往往落后于受支持版本。先按 Debian / Ubuntu 官方安装指南 为当前发行版配置 RabbitMQ 团队维护的签名密钥和 APT 源,再安装并验证服务:

sudo apt-get update
sudo apt-get install rabbitmq-server -y --fix-missing
sudo systemctl enable --now rabbitmq-server
sudo rabbitmq-diagnostics status
sudo rabbitmq-plugins enable rabbitmq_management

不要把其他 Ubuntu / Debian 版本的仓库代号直接复制到当前系统,也不要在未核对版本时手工下载插件文件。

macOS 安装(使用 Homebrew)

  1. 安装 Homebrew(如果未安装):/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  2. 安装 RabbitMQ:brew install rabbitmq
  3. 启动服务:brew services start rabbitmq

启用插件

D:\Program Files\FlyEnv\PhpWebStudy-Data\app\rabbitmq-4.0.9\sbin
$ rabbitmq-plugins.bat enable rabbitmq_management
Enabling plugins on node rabbit@PC-20250304DZOI:
rabbitmq_management
The following plugins have been configured:
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_web_dispatch
Applying plugin configuration to rabbit@PC-20250304DZOI...
Plugin configuration unchanged.

启动 RabbitMQ

$ rabbitmq-server.bat
=INFO REPORT==== 4-Sep-2025::15:53:55.045000 ===
    alarm_handler: {set,{system_memory_high_watermark,[]}}
2025-09-04 15:53:57.062000+08:00 [notice] <0.45.0> Application syslog exited with reason: stopped
2025-09-04 15:53:57.069000+08:00 [notice] <0.213.0> Logging: switching to configured handler(s); following messages may not be visible in this log output

  ##  ##      RabbitMQ 4.0.9
  ##  ##
  ##########  Copyright (c) 2007-2025 Broadcom Inc and/or its subsidiaries
  ######  ##
  ##########  Licensed under the MPL 2.0. Website: https://rabbitmq.com

  Erlang:      28.0.2 [jit]
  TLS Library: OpenSSL - OpenSSL 3.1.0 14 Mar 2023
  Release series support status: see https://www.rabbitmq.com/release-information

  Doc guides:  https://www.rabbitmq.com/docs
  Support:     https://www.rabbitmq.com/docs/contact
  Tutorials:   https://www.rabbitmq.com/tutorials
  Monitoring:  https://www.rabbitmq.com/docs/monitoring
  Upgrading:   https://www.rabbitmq.com/docs/upgrade

  Logs: <stdout>
        c:/Users/JT/AppData/Roaming/RabbitMQ/log/rabbit@PC-20250304DZOI.log

  Config file(s): (none)

  Starting broker... completed with 3 plugins.

打开管理界面查看

  • 地址: http://localhost:15672/
  • 账号密码: guest/guest RabbitMQ 管理界面

ThinkPHP 示例

安装 Composer 包

composer require php-amqplib/php-amqplib

生产者

<?php
namespace app\controller;

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use think\facade\Db;
use Throwable;

class Order
{
    public function createOrder()
    {
        try {
            $result = Db::name('order')->insertGetId([
                'user_id'       => 1,
                'price'         => 99.99,
                'createTime'    => date('Y-m-d H:i:s')
            ]);

            // 定义RabbitMQ连接配置
            $config = [
                'host' => '127.0.0.1',
                'port' => 5672,
                'user' => 'guest',
                'password' => 'guest',
                'vhost' => '/'
            ];

            // 创建连接
            $connection = new AMQPStreamConnection(
                $config['host'],
                $config['port'],
                $config['user'],
                $config['password'],
                $config['vhost']
            );

            // 创建通道
            $channel = $connection->channel();

            // 声明队列(持久化以提高可靠性)
            $channel->queue_declare('order_queue', false, true, false, false); // durable: true

            // 创建消息(设置持久化)
            $message = new AMQPMessage(json_encode([
                'orderId'      => $result,
                'expireTime'   => 15
            ]), ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]);

            // 发布消息
            $channel->basic_publish($message, '', 'order_queue');

            // 关闭通道和连接
            $channel->close();
            $connection->close();

            echo "订单创建成功,订单ID为:{$result}";
        } catch (Throwable $e) {
            // 错误处理:记录日志或回滚
            error_log("Order creation failed: " . $e->getMessage());
            echo "订单创建失败: " . $e->getMessage();
        }
    }
}

消费者

# app\command\OrderConsumer.php
<?php
declare (strict_types = 1);

namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\Output;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

class OrderConsumer extends Command
{
    protected function configure()
    {
        // 指令配置
        $this->setName('OrderConsumer')
            ->setDescription('RabbitMQ订单消费');
    }

    protected function execute(Input $input, Output $output)
    {
        try {
            // 连接RabbitMQ配置
            $config = [
                'host' => '127.0.0.1',
                'port' => 5672,
                'user' => 'guest',
                'password' => 'guest',
                'vhost' => '/'
            ];

            $connection = new AMQPStreamConnection(
                $config['host'],
                $config['port'],
                $config['user'],
                $config['password'],
                $config['vhost']
            );

            $channel = $connection->channel();

            // 声明队列(持久化)
            $channel->queue_declare('order_queue', false, true, false, false);

            // 定义回调函数
            $callback = function (AMQPMessage $message) use ($output) {
                try {
                    $data = json_decode($message->getBody(), true);
                    $orderId = $data['orderId'] ?? null; // 修复键名一致性

                    if ($orderId === null) {
                        throw new \Exception("Invalid message format");
                    }

                    $output->writeln('OrderConsumer: ' . $orderId);

                    // 手动确认消息
                    $message->ack();
                } catch (\Throwable $e) {
                    // 处理失败:拒绝消息并重新入队或移到死信队列
                    $message->nack(true); // requeue: true
                    $output->writeln('Error processing message: ' . $e->getMessage());
                }
            };

            // 消费消息(no_ack = false 表示手动确认)
            $channel->basic_consume('order_queue', '', false, false, false, false, $callback);

            // 循环等待消息
            while ($channel->is_consuming()) {
                $channel->wait();
            }

            // 清理
            $channel->close();
            $connection->close();
        } catch (\Throwable $e) {
            $output->writeln('Consumer error: ' . $e->getMessage());
        }
    }
}

# config\console.php
<?php
// +----------------------------------------------------------------------
// | 控制台配置
// +----------------------------------------------------------------------
return [
    // 指令定义
    'commands' => [
        'orderConsumer' => 'app\command\OrderConsumer',
    ],
];

性能考虑

  • 连接重用:在生产环境中,使用连接池避免频繁创建连接。
  • 批量处理:消费者可批量确认消息以提高吞吐量。
  • 预取消息:使用 basic_qos 限制预取消息数,防止消费者过载,例如 $channel->basic_qos(null, 1, null);

查看指令

$ php think 
version 8.1.3

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -V, --version         Display this console version
  -q, --quiet           Do not output any message
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  clear             Clear runtime file
  help              Displays help for a command
  list              Lists commands
  orderConsumer     RabbitMQ订单消费
  run               PHP Built-in Server for ThinkPHP
  version           show thinkphp framework version
 make
  make:command      Create a new command class
  make:controller   Create a new resource controller class
  make:event        Create a new event class
  make:listener     Create a new listener class
  make:middleware   Create a new middleware class
  make:model        Create a new model class
  make:service      Create a new Service class
  make:subscribe    Create a new subscribe class
  make:validate     Create a validate class
 optimize
  optimize:config   Build config cache.
  optimize:route    Build app route cache.
  optimize:schema   Build database schema cache.
 route
  route:list        show route list.
 service
  service:discover  Discover Services for ThinkPHP
 vendor
  vendor:publish    Publish any publishable assets from vendor packages

启动消费者监听

$ php think orderConsumer

测试

生产消息

浏览器打开 http://127.0.0.1:8000/createOrder

订单创建成功

查看消费者消息

消费者接收消息

常见问题排查

现象 优先检查
消息没有进入队列 Exchange、routing key、binding 是否匹配,mandatory 返回是否被处理
队列有堆积 消费速率、未确认消息、prefetch、消费者异常和下游耗时
消息重启后丢失 Exchange / Queue 是否 durable,消息是否 persistent
消息重复消费 是否在业务成功前确认,消费逻辑是否按业务键幂等
消费者内存持续增长 prefetch 是否过大,单条消息是否过大,连接和 channel 是否泄漏

排查时先看管理界面的 Ready、Unacked、Publish、Deliver/Ack 速率,再结合生产者确认和消费者日志定位,不要只看队列总数。

最佳实践

  1. 生产者开启 Publisher Confirms,并处理无法路由的消息;
  2. 队列、交换机和需要恢复的消息都显式持久化;
  3. 消费者在业务处理成功后手动 Ack,失败按异常类型选择重试、拒绝或死信;
  4. 使用业务唯一键实现幂等,不能假设消息只投递一次;
  5. 为重试设置次数和退避时间,避免坏消息无限回队列;
  6. 根据处理耗时设置 prefetch,同时监控 Ready、Unacked、连接数和磁盘告警。

参考资源