RabbitMQ 的整体架构是怎样的?

#中间件#面试八股

RabbitMQ 的整体架构是怎样的?

结论:RabbitMQ 是 AMQP 0-9-1 的实现,消息不直接发给队列,而是先到交换机(Exchange),由绑定(Binding)和路由键(Routing Key)决定投递到哪些队列。Broker 内部用虚拟主机(vhost)做租户隔离,一条 TCP 连接(Connection)上可复用多个信道(Channel),每个队列由独立的 Erlang 进程承载。

一、消息流转

Producer
   │ basicPublish(exchange, routingKey, body)
   ▼
┌──────────────── Broker ──────────────────────────────┐
│  vhost: /order                                       │
│                                                      │
│  Exchange "order.ex"  ──binding(order.#)──> Queue A  │──> Consumer 1
│   (topic)             ──binding(order.pay)─> Queue B │──> Consumer 2
│                                                      │
│  每个 Queue 是独立的 Erlang 进程,消息在进程内排队       │
└──────────────────────────────────────────────────────┘

四个交换机类型决定了"一条消息能扩散到几个队列":

类型路由规则典型用途
directrouting key 完全相等点对点任务分发
fanout忽略 routing key,广播到所有绑定队列缓存刷新、配置通知
topic按 *(一个词)与 #(零到多个词)通配按业务维度订阅,如 order.*.created
headers按消息头键值匹配多条件路由,性能较差,少用

默认交换机(名字为空字符串)是 direct 类型,routing key 直接写队列名,这也是"不声明交换机也能发消息"的原因。

二、核心组件

组件作用注意点
Connection一条 TCP 长连接创建成本高,进程内应复用
Channel连接上的虚拟信道非线程安全,建议每线程一个
vhost逻辑隔离单元权限、交换机、队列都按 vhost 隔离
Exchange路由规则声明为 durable 才能重启存活
Queue消息存储单队列单进程,吞吐有上限
Binding交换机与队列的关联绑定关系存元数据,集群内全量复制

因为每个队列是一个 Erlang 进程、内部近似单线程处理,单队列的吞吐存在天花板(万级每秒)。要提升吞吐只能增加队列数并让消费者分散,而不是给某个队列加消费者。

三、声明与绑定的代码

ConnectionFactory f = new ConnectionFactory();
f.setHost("10.0.0.21");
f.setVirtualHost("/order");
f.setUsername("app");
f.setPassword("***");

try (Connection conn = f.newConnection(); Channel ch = conn.createChannel()) {
    // durable=true 让交换机/队列在重启后仍然存在
    ch.exchangeDeclare("order.ex", BuiltinExchangeType.TOPIC, true);
    ch.queueDeclare("order.pay.q", true, false, false,
            Map.of("x-queue-type", "quorum"));   // 仲裁队列,见高可用一章
    ch.queueBind("order.pay.q", "order.ex", "order.pay.*");
}
# 观察拓扑与积压
rabbitmqctl list_exchanges name type durable
rabbitmqctl list_queues -p /order name messages consumers
rabbitmqctl list_bindings -p /order

四、常见追问

  • Channel 为什么不共享? 信道上的 confirm、事务、prefetch 都是连接级状态,多线程共用一个 Channel 会出现帧交错与确认错乱,官方建议每线程一个。
  • vhost 和数据库的库是一回事吗? 类似但更彻底:不同 vhost 的交换机、队列、权限、策略互不可见,多租户或环境隔离常用它。
  • 消息在队列里是什么顺序? FIFO,但仅限于单队列;多个队列之间没有全局顺序,需要顺序时只能用单队列单消费者。
  • 交换机类型能改吗? 不能。类型是声明时确定的属性,修改需要删除重建并重新绑定,生产环境应通过"新交换机 + 迁移绑定"的方式灰度。

小结

  • RabbitMQ 的路由核心是"交换机 + 绑定 + routing key",队列只是消息的落脚点。
  • vhost 做隔离、Connection 复用、Channel 每线程一个,是使用层面的三条基本纪律。
  • 队列是独立 Erlang 进程,单队列吞吐有限,扩展手段是加队列而不是加消费者。
  • 声明时必须显式设置 durable,否则重启后拓扑消失,消息自然也就没了。
笔记加载中…