Files
cs-note/hhs/MQ/07-主流MQ对比/23-RabbitMQ.md
T
2026-05-24 20:51:06 +08:00

215 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [MQ, RabbitMQ, 消息队列, AMQP]
create time: 2026-05-24 19:52
---
# RabbitMQ
## 概述
RabbitMQ 是基于 Erlang/OTP 实现的开源消息代理,实现了 AMQP 0-9-1 协议。它以灵活的路由能力、丰富的协议支持和成熟的插件生态著称,是企业级消息中间件的经典选择。
## 正文
### 1. 架构与 AMQP 协议
RabbitMQ 使用 Erlang 语言编写,天然具备高并发和软实时特性。其核心概念围绕 AMQP 0-9-1 协议展开:
- **Connection**:TCP 长连接,客户端与 Broker 之间的一条物理连接,包含认证和 TLS 握手。
- **Channel**:在 Connection 上的多路复用虚拟连接。多个 Channel 共享同一个 TCP 连接,避免频繁创建/销毁 TCP 的开销。一个线程对应一个 Channel。
- **Virtual Host(vhost)**:逻辑隔离单元,类似数据库中的 schema,不同 vhost 的 Exchange 和 Queue 完全隔离。
- **Exchange**:接收 Producer 发来的消息,根据路由规则分发到 Queue。
- **Queue**:消息的最终存储位置,Consumer 从 Queue 拉取或被推送消息。
- **Binding**:连接 Exchange 和 Queue 的规则,定义路由条件。
```mermaid
graph LR
Producer["Producer"] -->|"publish"| Exchange["Exchange"]
Exchange -->|"binding rule"| Q1["Queue A"]
Exchange -->|"binding rule"| Q2["Queue B"]
Q1 -->|"consume"| C1["Consumer A"]
Q2 -->|"consume"| C2["Consumer B"]
style Exchange fill:#4A90D9,color:#fff
```
### 2. 四种 Exchange 类型
Exchange 的类型决定了消息如何路由到 Queue,这是 RabbitMQ 最灵活的设计:
**Direct Exchange**:精确匹配。消息的 Routing Key 与 Binding Key 完全一致时才路由。适合点对点定向投递。
**Fanout Exchange**:广播模式。忽略 Routing Key,将消息投递到所有绑定的 Queue。适合事件通知、广播场景。
**Topic Exchange**:通配符匹配。Routing Key 和 Binding Key 支持 `*`(匹配一个词)和 `#`(匹配零或多个词)。适合按主题分类订阅,如 `order.*.created`。
**Headers Exchange**:基于消息 Header 属性匹配(而非 Routing Key)。支持 `x-match=all`(所有条件匹配)和 `x-match=any`(任一条件匹配)。灵活但性能不如前三种。
```mermaid
graph TB
P["Producer"] --> EX_D["Direct Exchange<br/>routing_key = order"]
P --> EX_F["Fanout Exchange<br/>广播"]
P --> EX_T["Topic Exchange<br/>order.*.created"]
EX_D -->|"rk = order"| QA["Queue A"]
EX_F --> QA
EX_F --> QB["Queue B"]
EX_F --> QC["Queue C"]
EX_T -->|"order.pay.created"| QB
EX_T -->|"order.ship.created"| QD["Queue D"]
style EX_D fill:#4A90D9,color:#fff
style EX_F fill:#7B68EE,color:#fff
style EX_T fill:#F5A623,color:#fff
```
> [!question] 生产环境中,什么时候该用 Topic Exchange 而不是 Direct Exchange?
> 当消费者需要按模式订阅一类消息时用 Topic。比如日志系统中,`log.error.*` 可以匹配所有 error 级别的日志,而 Direct 只能精确匹配一个 routing key。如果你的路由规则是固定的、一一对应的,Direct 更简单高效。
### 3. 高级特性
**TTL(Time-To-Live)**:
- **消息 TTL**:通过 `x-message-ttl` 设置队列级别 TTL,或在发布时通过 `expiration` 属性设置单条消息 TTL。过期消息被丢弃或进入死信队列。
- **队列 TTL**:通过 `x-expires` 设置,队列在空闲(无消费者、无声明)超过指定时间后自动删除。适合临时队列。
**死信队列(DLX, Dead Letter Exchange)**:消息在以下情况会成为"死信":被消费者拒绝(reject/nack 且 requeue=false)、消息 TTL 到期、队列达到最大长度。死信会被路由到配置的 DLX 对应的队列,实现延迟重试、异常消息归档等模式。
**延迟队列**:RabbitMQ 原生不支持延迟消息(不像 RocketMQ 有延迟级别)。社区提供了 `rabbitmq_delayed_message_exchange` 插件,消息在 Exchange 中暂存,到期后再投递到目标队列。常用于订单超时取消、定时提醒等场景。
> [!question] 死信队列和延迟队列有什么关系?
> 延迟队列的一种经典实现方式就是"利用消息 TTL + 死信队列":将消息发到一个没有消费者的队列并设置 TTL,到期后消息变成死信,被路由到 DLX 绑定的真正消费队列。但这有个缺点——队列头部消息未过期会阻塞后面的消息(因为 RabbitMQ 只检查队头)。延迟消息插件则用定时器解决这个问题。
### 4. 集群模式
RabbitMQ 支持多种集群模式,可靠性逐步递增:
**普通集群**:所有节点共享元数据(Exchange、Binding 等),但 Queue 的数据只存在于声明它的那个节点。其他节点收到消息后需要跨节点转发,存在单点风险。
**镜像队列(Mirrored Queue)**:在普通集群基础上,将 Queue 数据同步到多个节点。一个 Master + 若干 Slave,所有读写都经过 Master。缺点是:所有操作都由 Master 串行处理,性能受限于 Master 节点;同步方式是"发一份拷贝",网络开销大;Slave 只是热备,不承担读流量。**这就是镜像队列性能差的根本原因——单 Master 瓶颈。**
**Quorum Queue**:基于 Raft 共识协议的队列类型,是镜像队列的现代替代方案。Leader 负责接收写入,消息被复制到多数派 Follower 后才确认。Follower 可以分担读流量(`x-queue-leader-locator=balanced`),Leader 故障后自动选举新 Leader。**Quorum Queue 的改进在于:Raft 共识比简单的主从复制更可靠,Follower 可以参与读操作,且日志复制有明确的多数派确认语义。**
> [!question] 思考题:RabbitMQ 的镜像队列为什么性能不好?Quorum Queue 是如何改进的?
>
> **提示**:镜像队列的核心问题是"所有流量都走 Master"——写入要 Master 确认,读取也要 Master 响应,Slave 只是被动同步。当 Master 成为瓶颈时,加 Slave 不能提升吞吐。Quorum Queue 用 Raft 日志复制替代简单的消息拷贝,写入由多数派确认(而非 Master 独占),且支持从 Follower 读取,分散了压力。
### 5. RabbitMQ Stream
Stream 是 RabbitMQ 3.9 引入的全新队列类型,对标 Kafka 的流式存储模型:
- 消息以日志形式持久化,支持多次回放(不像普通队列消费即删除)。
- 使用 offset 而非 ACK 来跟踪消费进度,支持从任意位置开始消费。
- 性能远超传统队列,适合高吞吐的日志/事件流场景。
- 通过 AMQP 1.0 或专用 Stream 协议访问。
Stream 本质上让 RabbitMQ 同时具备了"传统消息队列"和"流处理平台"两种能力。
### 6. 插件生态
RabbitMQ 的可扩展性通过插件机制实现:
| 插件 | 作用 |
|------|------|
| **Management UI** | Web 管理界面,监控队列、Exchange、连接,管理策略和权限 |
| **Prometheus 插件** | 暴露 Prometheus 格式的监控指标,配合 Grafana 看板 |
| **Shovel** | 单向消息搬运,将消息从一个 Broker 转发到另一个,适合跨机房同步 |
| **Federation** | 跨集群消息联邦,支持 Exchange/Queue 级别的联邦,比 Shovel 更灵活,支持按需连接 |
### 7. 消息确认机制
RabbitMQ 提供了完整的可靠投递保证:
**Publisher Confirm(发布确认)**:Producer 将 Channel 设置为 confirm 模式后,Broker 成功将消息写入所有镜像(或 Quorum 多数派)后返回确认。支持同步 confirm 和异步 confirm(批量/单条)。注意区分 confirm 和事务——confirm 更轻量,性能更好。
**Consumer ACK(消费确认)**:消费者处理完消息后显式调用 `basicAck`,Broker 才从队列中删除消息。如果消费者崩溃未 ACK,消息会被重新投递。支持 `basicNack` / `basicReject` 拒绝消息并决定是否 requeue。
**Return 机制**:当消息无法路由到任何 Queue(没有匹配的 Binding),且 `mandatory=true` 时,Broker 通过 Return 回调通知 Producer。配合 `alternate-exchange` 可以将无法路由的消息转入备用 Exchange。
```go
// 使用 amqp091-go 的完整发布确认 + 手动 ACK 示例
package main
import (
"context"
"fmt"
amqp "github.com/rabbitmq/amqp091-go"
"log"
"time"
)
func main() {
// 建立连接
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
ch, err := conn.Channel()
if err != nil {
log.Fatal(err)
}
defer ch.Close()
// 声明 Quorum Queue(生产环境推荐)
_, err = ch.QueueDeclare("order-queue", true, false, false, false, amqp.Table{
"x-queue-type": "quorum",
})
if err != nil {
log.Fatal(err)
}
// ===== 发布确认 =====
// 将 Channel 设置为 confirm 模式
if err := ch.Confirm(false); err != nil {
log.Fatal(err)
}
// 获取确认通知 channel
confirms := ch.NotifyPublish(make(chan amqp.Confirmation, 1))
// 发布消息
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = ch.PublishWithContext(ctx, "", "order-queue", false, false, amqp.Publishing{
ContentType: "application/json",
Body: []byte(`{"order_id": "1001", "amount": 99.9}`),
DeliveryMode: amqp.Persistent, // 持久化消息
})
if err != nil {
log.Fatal(err)
}
// 等待 Broker 确认
conf := <-confirms
if !conf.Ack {
log.Fatal("message was nacked by broker")
}
fmt.Println("publish confirmed")
// ===== 消费端手动 ACK =====
msgs, err := ch.Consume("order-queue", "", false, false, false, false, nil)
if err != nil {
log.Fatal(err)
}
for msg := range msgs {
fmt.Printf("received: %s\n", msg.Body)
// 处理完成后手动确认,false 表示只确认当前消息
if err := msg.Ack(false); err != nil {
log.Printf("ack failed: %v", err)
}
}
}
```
## 关联笔记
- [[03-协议与标准/5-AMQP-协议|AMQP 协议]]
- [[04-存储引擎/11-RabbitMQ-消息存储|RabbitMQ 消息存储]]
- [[05-可靠性保障/12-MQ-消息确认与持久化|MQ 消息确认与持久化]]
- [[05-可靠性保障/16-MQ-死信队列与消息回溯|MQ 死信队列与消息回溯]]
- [[06-高级特性/17-MQ-延迟消息与定时消息|MQ 延迟消息与定时消息]]
- [[12-架构与实战/44-MQ-高可用架构|MQ 高可用架构]]
- [[07-主流MQ对比/27-MQ-选型对比|MQ 选型对比]]