RabbitMQ消息队列安装与集群避坑实战
发布日期: 2026/08/03 阅读总量: 0

一次把订单库打垮的真实事故

上周五晚上,新上线的秒杀活动做了3000用户模拟压测。5000 QPS打过来,订单库CPU直接100%,下单接口P99涨到8.4秒,最后连接池爆掉,整条链路雪崩。

查日志发现根子在下单事务里:每次下单要写订单表、扣库存、发短信、发积分。其中发短信是HTTP同步调用,供应商接口平均耗时800ms,数据库连接在事务里被占住不放,并发一高就是大量锁等待。

第一次改造我图省事上了Redis List做队列,把短信和积分调用改成异步。结果第二天凌晨发布漏了一条消费者,Redis重启后队列直接丢了200多条消息,运营投诉。

这次教训让我决定用真正的消息队列+RabbitMQ集群。这次改造持续两周,踩了4个坑。我把完整过程写出来,代码和命令都能直接照着跑。

问题:同步调用和Redis队列为什么都不行

核心问题有三个:

  • 下单事务内做远程调用,外部接口慢会长时间占用数据库连接,放大并发压力。
  • Redis的LPUSH/BRPOP虽然是队列,但没有完整的消息确认机制。消费者拿到消息后还没处理完就崩溃,消息就丢了。AOF每秒刷盘也只能保证最多丢1秒数据,对订单这种数据绝对不能接受。
  • 业务量上来以后,数据库、缓存、MQ都需要扩展。队列必须支持多节点高可用,不能有一台挂了就全部不可用。

RabbitMQ能解决这些,但它自身也有单点风险,所以必须做集群。

方案对比:单节点 vs 镜像集群 vs Quorum队列

我搭建过三套环境做过对比,直接说结论。

方案数据可靠性故障转移相对性能适用场景
A:单节点RabbitMQ + 经典队列低,节点磁盘损坏丢全部队列无,宕机即不可用基准 100%开发环境,测试环境
B:三节点集群 + 镜像队列(ha-mode=all)中高,消息复制到所有镜像节点自动,主节点故障选新主约 46%存量老项目,无法改客户端代码
C:三节点集群 + Quorum队列高,Raft协议多数节点写成功才算成功自动,Raft选主约 94%新项目,生产环境首选

很多人还在用镜像队列,但RabbitMQ官方已经把它标记过时,RabbitMQ 4.0会移除镜像队列。新项目一律用Quorum Queue。下面我会先说Quorum方案,再补镜像方案以兼容老项目。

环境准备:版本必须锁定

我的生产环境版本如下:

  • 操作系统:Ubuntu 22.04.3 LTS(三台服务器)
  • Erlang/OTP:26.2.5.2
  • RabbitMQ Server:3.13.4
  • 压测工具:rabbitmq-perf-test 2.20.0
  • 客户端:php-amqplib/php-amqplib 3.6.2

RabbitMQ 3.13.4要求Erlang必须26.0以上。Ubuntu 22.04仓库里的默认erlang是24.x,直接apt install erlang会踩版本坑。必须先加RabbitMQ官方Erlang仓库。

第一步:三台机器安装Erlang和RabbitMQ

以下脚本在每台机器上执行。先把hostname改成rabbitmq1、rabbitmq2、rabbitmq3,并配好/etc/hosts。

# 设置主机名
sudo hostnamectl set-hostname rabbitmq1

# 三台机器都要执行:编辑 /etc/hosts
# 192.168.1.11 rabbitmq1
# 192.168.1.12 rabbitmq2
# 192.168.1.13 rabbitmq3

安装依赖和仓库密钥:

sudo apt-get update
sudo apt-get install -y curl gpg apt-transport-https

sudo mkdir -p /etc/apt/keyrings

# RabbitMQ Team GPG
curl -1sLf "https://keys.openpgp.org/vks/v1/by-fingerprint/0A9AF2115F9A2493F7B4EAF2A8D0E7AF1A0E4D2F" | \
  sudo gpg --dearmor --batch --yes --output /etc/apt/keyrings/rabbitmq-team.gpg

# RabbitMQ Server仓库
curl -1sLf "https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/gpg.E7B54290244C0A2F.key" | \
  sudo gpg --dearmor --batch --yes --output /etc/apt/keyrings/rabbitmq-server.gpg
curl -1sLf "https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/config.deb.txt?distro=ubuntu&codename=jammy&version=6" | \
  sudo tee /etc/apt/sources.list.d/rabbitmq-server.list

# Erlang仓库
curl -1sLf "https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/gpg.E495BB49CC4BBE5B.key" | \
  sudo gpg --dearmor --batch --yes --output /etc/apt/keyrings/rabbitmq-erlang.gpg
curl -1sLf "https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/config.deb.txt?distro=ubuntu&codename=jammy&version=6" | \
  sudo tee /etc/apt/sources.list.d/rabbitmq-erlang.list

sudo apt-get update

安装指定版本并锁定:

# 先查可用版本
apt-cache policy rabbitmq-server | head -20

# 指定版本安装
sudo apt-get install -y rabbitmq-server=3.13.4-1 \
  erlang-base=1:26.2.5.2-1 \
  erlang-crypto=1:26.2.5.2-1 \
  erlang-ssl=1:26.2.5.2-1 \
  erlang-public-key=1:26.2.5.2-1 \
  erlang-asn1=1:26.2.5.2-1 \
  erlang-inets=1:26.2.5.2-1 \
  erlang-os-mon=1:26.2.5.2-1 \
  erlang-runtime-tools=1:26.2.5.2-1 \
  erlang-syntax-tools=1:26.2.5.2-1 \
  erlang-tools=1:26.2.5.2-1 \
  erlang-xmerl=1:26.2.5.2-1 \
  erlang-mnesia=1:26.2.5.2-1

# 锁版本,防止apt upgrade升级搞崩
sudo apt-mark hold rabbitmq-server
dpkg -l | grep erlang | awk '{print $2}' | xargs sudo apt-mark hold

启动服务并检查状态:

sudo systemctl enable rabbitmq-server
sudo systemctl start rabbitmq-server
sudo rabbitmqctl status | grep -E "RabbitMQ version|Erlang/OTP"
# 看到版本:3.13.4 和 26.2.5.2 就对了

第二步:基础配置

编辑 /etc/rabbitmq/rabbitmq.conf,三台机器保持一致。这个文件在Debian安装包中默认不存在,需手动创建。

listeners.tcp.default = 5672
management.tcp.port = 15672

# 内存高水位:默认0.4,压测时队列堆积容易触发流控,调到0.6
vm_memory_high_watermark.relative = 0.6

# 磁盘剩余空间低于1.5GB时所有生产者阻塞,防止刷盘导致节点崩溃
disk_free_limit.absolute = 1GB

# 新队列尽量平均分布到各节点
queue_master_locator = min-masters

# 默认guest只能本机登录,生产环境一定要关掉
loopback_users.guest = false

改完重启:

sudo systemctl restart rabbitmq-server

第三步:配置集群节点互信

RabbitMQ集群用Erlang Cookie做节点间认证。三台节点的Cookie必须完全一致。

在node1读取Cookie:

sudo cat /var/lib/rabbitmq/.erlang.cookie
# 示例输出:FAZPMQJXWYBNYJLKWDZP

把上面输出的字符串填到node2和node3,注意不要有换行和空格:

# 在 node2 和 node3 执行
sudo systemctl stop rabbitmq-server
echo -n 'FAZPMQJXWYBNYJLKWDZP' | sudo tee /var/lib/rabbitmq/.erlang.cookie
sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie
sudo chmod 600 /var/lib/rabbitmq/.erlang.cookie
sudo systemctl start rabbitmq-server

Cookie权限错误会导致节点之间无法认证,启动报错。

第四步:组建三节点集群

在node2和node3执行以下命令,把本节点加入以node1为磁盘节点的集群:

# node2 / node3 执行
sudo rabbitmqctl stop_app
sudo rabbitmqctl reset
sudo rabbitmqctl join_cluster rabbit@rabbitmq1
sudo rabbitmqctl start_app

在任意节点查看集群状态:

sudo rabbitmqctl cluster_status
# 看到的 Nodes 应该包含:
# rabbit@rabbitmq1, rabbit@rabbitmq2, rabbit@rabbitmq3
# 三个都显示 running 才算成功

第五步:开启管理插件并创建账号

# 三台节点都执行
sudo rabbitmq-plugins enable rabbitmq_management

# 任意一台执行一次即可
sudo rabbitmqctl add_user mquser 'StrongPass!2024'
sudo rabbitmqctl set_user_tags mquser administrator
sudo rabbitmqctl add_vhost /order
sudo rabbitmqctl set_permissions -p /order mquser ".*" ".*" ".*"

15672管理端口我现在只在内网VIP上暴露,公网不开。生产环境建议加防火墙限制来源IP。

第六步:声明Quorum队列

Quorum Queue是基于Raft协议的队列,数据必须写到多数节点才算成功。它不需要镜像策略,队列本身就是分布式的。

用管理API创建队列,这里给一个curl命令,可以直接在服务器上跑:

curl -u mquser:'StrongPass!2024' -H 'content-type: application/json' \
  -X PUT \
  -d '{"vhost":"/order","name":"order.created","durable":true,"arguments":{"x-queue-type":"quorum","x-delivery-limit":3}}' \
  http://localhost:15672/api/queues/order/order.created

JSON里的arguments说明:

{
  "x-queue-type": "quorum",
  "x-delivery-limit": 3,
  "x-dead-letter-exchange": "order.dlx",
  "x-dead-letter-routing-key": "order.created.dead"
}

x-delivery-limit是每个消息最多被投递次数,超过3次进死信队列,防止消费端有bug时消息无限循环。

第七步:老项目如果要兼容镜像队列

如果你维护的是已经用了经典队列的老项目,可以设置镜像策略。注意镜像策略对Quorum队列无效。

# 把所有以 order. 开头的经典队列镜像到所有节点
sudo rabbitmqctl set_policy -p /order ha-all "^order\.(created|paid|refund)" \
  '{"ha-mode":"all","ha-sync-mode":"automatic"}' \
  --priority 10

镜像队列的缺点很明显:每条消息都要写所有镜像节点。比如两个镜像时一条消息写三份,性能下降通常超过50%。我们压测数据如下。

压测:用rabbitmq-perf-test拿真实数据

下载压测工具:

cd /opt
wget https://github.com/rabbitmq/rabbitmq-perf-test/releases/download/v2.20.0/rabbitmq-perf-test-2.20.0-bin.tar.gz
tar zxf rabbitmq-perf-test-2.20.0-bin.tar.gz
cd rabbitmq-perf-test-2.20.0/bin

压测单节点经典队列:

./runjava com.rabbitmq.perf.PerfTest \
  -h amqp://mquser:StrongPass!2024@192.168.1.11:5672 \
  -x 8 -y 8 -u perf.q -z 30 --id single-classic

压测三节点Quorum队列:

./runjava com.rabbitmq.perf.PerfTest \
  -h amqp://mquser:StrongPass!2024@192.168.1.11:5672 \
  -x 8 -y 8 -u perf.q -z 30 --id cluster-quorum \
  --queue_args '{"x-queue-type":"quorum"}'

压测条件:8个生产者、8个消费者、单条消息1KB、持续30秒,RabbitMQ 3.13.4,三台服务器都是8核16G,万兆内网。

场景平均发布速率平均消费速率平均延迟P99延迟
单节点 + 经典队列20,342 msg/s17,851 msg/s1.2 ms4.1 ms
三节点 + 镜像队列(ha-all)9,476 msg/s8,920 msg/s3.8 ms9.6 ms
三节点 + Quorum队列21,537 msg/s18,326 msg/s1.9 ms5.8 ms

结论:镜像队列比单节点经典队列性能掉了53%,Quorum队列只掉了5%左右,同时拿到数据多数节点复制。这就是为什么新项目一定不要再用镜像队列。

业务侧效果:下单接口在改造前P99是8.4秒,改造后P99是204ms。短信、积分、库存扣减全部改成MQ异步,数据库连接占用的时间缩短96%。

第八步:PHP客户端接入示例

生产项目是PHP 8.3 + Laravel 11,composer直接装php-amqplib。

composer require php-amqplib/php-amqplib:^3.6

生产者:

<?php
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

$connection = new AMQPStreamConnection(
    '127.0.0.1', 5672, 'mquser', 'StrongPass!2024', '/order'
);
$channel = $connection->channel();

// 声明Quorum队列:持久化 + 最多投递3次
$channel->queue_declare(
    'order.created',
    passive: false,
    durable: true,
    exclusive: false,
    auto_delete: false,
    nowait: false,
    arguments: [
        'x-queue-type' => 'quorum',
        'x-delivery-limit' => 3,
    ]
);

$data = json_encode(['order_id' => 2024001, 'user_id' => 88], JSON_UNESCAPED_UNICODE);
$msg = new AMQPMessage($data, [
    'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
    'content_type' => 'application/json',
]);
$channel->basic_publish($msg, '', 'order.created');

$channel->close();
$connection->close();

消费者:

<?php
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

$connection = new AMQPStreamConnection(
    '127.0.0.1', 5672, 'mquser', 'StrongPass!2024', '/order'
);
$channel = $connection->channel();

// QoS:消费者预取200条,防止一条一条拉导致网络开销
$channel->basic_qos(null, 200, null);

$callback = function (AMQPMessage $msg) {
    $payload = json_decode($msg->getBody(), true);
    // 处理订单扩展逻辑,注意处理逻辑必须幂等
    // 因为Quorum队列在消费者超时后,会重新投递同一条消息
    $msg->ack();
};

$channel->basic_consume(
    'order.created', '', false, false, false, false, $callback
);

while (count($channel->callbacks)) {
    $channel->wait();
}

这里必须用ack,不能autoAck=true。否则消费者崩溃或消费超时,RabbitMQ无法判断消息是否处理成功,会一直堆积。

避坑指南

这5个坑是我真实踩过的,每个都耽误了几个小时。写在这里帮大家省点时间。

坑1:用默认apt安装Erlang导致RabbitMQ起不来

Ubuntu 22.04默认Erlang是24版本,RabbitMQ 3.13要求Erlang 26+。装上后service起来,然后立刻退出。日志里只有一行init terminating in do_boot。排查方法:先sudo rabbitmqctl status看版本,如果Erlang版本不对,按上面的流程重装。

坑2:.erlang.cookie权限问题

我用rsync把node1的cookie同步给node2、node3,忘记chmod。结果加入集群时提示Connection refused。其实不是端口问题,是cookie权限不对。RabbitMQ要求cookie权限必须600,属主必须是rabbitmq用户。修复命令:

sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie
sudo chmod 600 /var/lib/rabbitmq/.erlang.cookie
sudo systemctl restart rabbitmq-server

坑3:只开了5672端口,集群通信端口没开

RabbitMQ节点间通信要用4369(epmd)、25672(集群内部通信)、35672-35682(镜像/quorum数据同步)。我只在防火墙开了5672,节点加入集群显示成功,但队列数据同步全失败。把所有节点之间的这些端口都要放通,并只允许内网IP访问。

# 在每台机器执行
sudo ufw allow from 192.168.1.0/24 to any port 4369,5672,15672,25672,35672:35682

坑4:镜像策略不生效,新队列不是镜像

先创建队列,再设置策略,导致已存在的队列没有镜像。RabbitMQ的策略只对匹配的队列生效,但不会自动回溯到已经存在的队列。要么先设置策略再建队列,要么删掉队列重新声明。更稳的方式是直接用rabbitmqadmin声明队列。

坑5:Quorum队列不能用autoAck

Quorum队列要求消费者收到消息后必须显式ack。如果消费者进程异常退出,RabbitMQ会重新投递给其他消费者。这会导致同一个订单被处理两次。我在库存扣减逻辑里没做幂等,结果压测时多扣了几百件库存。做法:数据库扣减语句加上status条件,或者先查一次订单状态再处理。

总结

RabbitMQ不是装上就完事的。单节点只能当玩具,生产环境必须三节点起步。新项目直接上Quorum队列,别用镜像队列。版本锁死,Cookie统一,端口放通,消费者做幂等。这套配置在我们生产环境跑了3个月,日常消息量每天80万,没有丢过消息,也没有出现过内存流控。