初识 MQ 与 RabbitMQ 学习笔记

1. 初识 MQ

1.1 同步调用

在当前的微服务架构中,基于 OpenFeign 的调用方式属于典型的同步调用。这种调用方式在业务流程简单时可满足需求,但随着业务复杂度提升,会逐渐暴露诸多问题。我们以 “余额支付功能” 为具体案例,深入分析同步调用的弊端。

1.1.1 余额支付同步调用流程

在余额支付场景中,采用 OpenFeign 同步调用的完整业务流程如下:

  1. 支付服务调用用户服务:完成用户账户余额的扣减操作(如用户购买 100 元商品,需从其账户中扣除 100 元);
  2. 支付服务更新自身数据:在支付服务的数据库中,将该笔支付对应的 “支付流水单” 状态从 “待支付” 更新为 “已支付”;
  3. 支付服务调用交易服务:通知交易服务,将对应的 “业务订单” 状态从 “待支付” 更新为 “已支付”。

这三个步骤必须依次执行,前一步完成后才能进入下一步,整个流程完全依赖同步调用的阻塞特性。

1.1.2 同步调用的三大核心问题

问题 1:扩展性差(违反开闭原则)

当前业务流程仅包含 “扣减余额 - 更新流水 - 更新订单” 三个步骤,但实际电商业务中,支付成功后的后续操作会不断增加。例如:

  • 产品经理提出 “支付成功后向用户发送短信通知”,需在流程中新增 “调用通知服务发送短信” 步骤;
  • 后续又提出 “支付成功后给用户赠送积分”(如支付 1 元得 1 积分,100 元订单赠 100 积分),需新增 “调用积分服务添加积分” 步骤;
  • 再后来可能增加 “支付成功后触发优惠券返还”“同步数据到数据分析平台” 等需求。

每次新增需求,都需要修改支付服务中已有的核心业务代码,不仅会导致支付服务逻辑越来越臃肿,还违反了软件开发的开闭原则(对扩展开放,对修改关闭),后续维护难度急剧上升。

问题 2:性能下降(响应时长累加)

同步调用的本质是 “调用方阻塞等待服务方返回结果”,因此整个业务的总响应时长等于所有远程调用时长与本地业务时长之和

假设各步骤耗时如下:

  • 调用用户服务扣减余额:50ms;
  • 支付服务更新流水单:20ms;
  • 调用交易服务更新订单:50ms;
  • 若新增 “调用通知服务发短信”:80ms;
  • 新增 “调用积分服务加积分”:40ms。

则最初的总耗时为 50+20+50=120ms,新增两个需求后总耗时变为 50+20+50+80+40=240ms。随着后续需求增加,总耗时会持续累加,严重影响用户体验(如用户支付后需等待 200ms 以上才能看到 “支付成功” 页面)。

问题 3:级联失败(局部故障导致整体不可用)

同步调用中,若某个下游服务出现故障,会直接导致整个支付流程失败。例如:

  • 场景 1:用户余额已成功扣减(钱已从用户账户划出),但调用交易服务时,交易服务因数据库宕机无法响应,此时整个支付流程会回滚,用户余额会被退回,导致 “钱扣了但订单未支付” 的矛盾场景;
  • 场景 2:支付成功后调用通知服务发短信,若通知服务因网络故障无法访问,同样会导致整个支付流程回滚,违背 “钱到账后必须确保订单生效” 的业务核心逻辑。

实际上,“发送短信”“更新积分” 等操作属于 “非核心流程”,即使暂时失败,也不应影响 “扣减余额 - 更新订单” 等核心流程的成功执行。同步调用的强依赖特性,导致了 “局部故障引发整体级联失败” 的风险。

1.1.3 同步调用问题总结

问题类型 核心表现 业务影响
扩展性差 新增需求需修改核心代码,违反开闭原则 代码臃肿,维护难度高,迭代效率低
性能下降 总响应时长 = 各步骤耗时之和,随需求增加累加 用户等待时间长,体验差
级联失败 下游非核心服务故障导致整体流程回滚 核心业务(如支付)不可靠,可能引发资金风险

要解决上述问题,必须用异步调用替代同步调用,通过 “消息通知” 的方式解耦服务间的依赖关系。

1.2 异步调用

异步调用并非直接调用下游服务接口,而是基于 “消息传递” 的方式实现服务间通信,核心是通过 “消息 Broker”(消息中间件)实现 “发送者” 与 “接收者” 的解耦。

1.2.1 异步调用的三大角色

  1. 消息发送者(Producer:原同步调用中的 “调用方”,负责将业务事件(如 “支付成功”)封装成 “消息”,并发送给消息 Broker;
    • 例:支付服务在 “扣减余额 + 更新流水单” 完成后,不再直接调用交易服务 / 通知服务,而是向 Broker 发送一条 “支付成功” 的消息。
  2. 消息 Broker(消息中间件):负责接收、存储、转发消息的中间服务,相当于 “消息中转站”;
    • 类比:可理解为微信服务器 —— 用户 A 发送消息给微信服务器(Broker),微信服务器暂存消息并转发给用户 B,A 无需等待 B 回复即可继续操作。
  3. 消息接收者(Consumer):原同步调用中的 “服务提供方”,负责从 Broker 订阅并接收消息,然后执行对应的业务逻辑;
    • 例:交易服务订阅 “支付成功” 消息,收到消息后更新订单状态;通知服务订阅该消息,收到后发送短信;积分服务订阅该消息,收到后添加用户积分。

1.2.2 异步调用在余额支付中的应用

基于异步调用重构后的余额支付流程如下:

  1. 核心流程(同步执行:支付服务仅执行 “调用用户服务扣减余额”“更新自身支付流水单状态” 两个核心步骤(耗时仅 50+20=70ms);
  2. 发送消息(异步触发):核心流程完成后,支付服务向 Broker 发送一条 “支付成功” 的消息(包含订单 ID、用户 ID、支付金额等关键信息);
  3. 下游服务异步处理
    • 交易服务:订阅 “支付成功” 消息,收到后更新订单状态;
    • 通知服务:订阅该消息,收到后向用户发送短信;
    • 积分服务:订阅该消息,收到后根据支付金额计算并添加积分;
    • 后续新增需求(如优惠券返还):只需让对应的服务(如优惠券服务)订阅该消息即可,无需修改支付服务代码。

1.2.3 异步调用的优势与不足

优势
  1. 耦合度极低:发送者仅需发送消息,无需关心谁是接收者、接收者有多少个;接收者仅需订阅消息,无需关心消息来自哪个发送者,服务间完全解耦;
  2. 性能大幅提升:核心流程仅包含本地业务与消息发送(消息发送耗时通常 < 10ms),总响应时长不再随下游服务数量增加而累加,用户体验显著提升;
  3. 业务扩展性强:新增下游业务(如积分、优惠券)时,只需新增接收者并订阅消息,无需修改发送者代码,完全符合开闭原则;
  4. 故障隔离(避免级联失败):非核心服务(如通知服务)故障时,消息会暂存在 Broker 中,待服务恢复后重新消费,不会影响核心流程(扣减余额、更新订单)的成功执行。
不足
  1. 依赖 Broker 的可靠性:若 Broker 宕机且未做高可用部署,消息可能丢失,导致下游服务无法处理;同时 Broker 的性能、安全性也直接决定了整个异步通信的稳定性;
  2. 架构复杂度提升:相比同步调用的 “线性流程”,异步调用需要考虑消息重试、消息幂等性(避免重复消费)、消息顺序性等问题,后期维护和调试难度更高(如定位 “消息未被消费” 的原因)。

1.3 技术选型(消息队列 MQ)

消息 Broker 的主流实现方案是消息队列(Message Queue,简称 MQ)。目前行业内常用的 MQ 产品有 4 种:ActiveMQ、RabbitMQ、RocketMQ、Kafka,各产品的特性与适用场景差异较大。

1.3.1 主流 MQ 产品对比

对比维度 ActiveMQ RabbitMQ RocketMQ Kafka
开发语言 Java Erlang(高并发特性优秀) Java Scala/Java
社区活跃度 较低(更新慢) 高(文档丰富,问题易解决) 中(阿里维护,国内活跃) 高(Apache 顶级项目,全球活跃)
可用性 一般(集群方案复杂) 高(支持镜像队列,易部署) 高(分布式架构,支持主从) 高(分布式分区,容错性强)
可靠性 支持,但配置复杂 高(支持消息持久化、确认机制) 高(支持事务消息、重试) 一般(默认异步刷盘,可能丢消息)
吞吐能力 低(万级 / 秒) 中(万级 ~ 十万级 / 秒) 高(十万级 / 秒) 极高(十万级 ~ 百万级 / 秒)
消息延迟 较高(毫秒级) 低(微秒级 ~ 毫秒级) 低(毫秒级) 低(毫秒级)
适用场景 小型项目、遗留系统 中大型项目、对延迟敏感场景 大型电商、金融(如订单、支付) 大数据、日志采集、实时分析

1.3.2 课程选型:RabbitMQ

综合考虑以下因素,课程选择 RabbitMQ 作为学习对象:

  1. 均衡性优秀:在可用性、可靠性、延迟、吞吐能力上均处于中高水平,无明显短板,适合大多数业务场景;
  2. 国内使用率高:据行业统计,国内中小型企业使用 RabbitMQ 的比例最高,就业场景中遇到的概率大;
  3. 文档与生态完善:官方文档详细,社区问题解决方案丰富,学习与调试成本低;
  4. 部署与运维简单:支持 Docker 快速部署,管理控制台功能强大,便于入门学习。

2. RabbitMQ

RabbitMQ 是基于 Erlang 语言开发的开源消息通信中间件,官网地址:https://www.rabbitmq.com/,其核心优势是 “高并发、高可靠、低延迟”,支持多种消息协议(如 AMQP、MQTT 等)。

2.1 安装(基于 Docker)

RabbitMQ 的安装推荐使用 Docker,步骤简单且环境隔离性好。

2.1.1 Docker 安装命令

1
2
3
4
5
6
7
8
9
10
11
docker run \
-e RABBITMQ_DEFAULT_USER=itheima \ # 设置RabbitMQ默认用户名(用于管理控制台登录)
-e RABBITMQ_DEFAULT_PASS=123321 \ # 设置RabbitMQ默认密码
-v mq-plugins:/plugins \ # 挂载插件目录(如后续需安装延迟队列插件)
--name mq \ # 容器名称
--hostname mq \ # 设置RabbitMQ的主机名(用于集群部署时识别节点)
-p 15672:15672 \ # 映射管理控制台端口(浏览器访问)
-p 5672:5672 \ # 映射消息通信端口(服务间调用)
--network hm-net\ # 加入自定义网络(若需与其他服务通信,如微服务)
-d \ # 后台运行容器
rabbitmq:3.8-management # 使用带管理控制台的镜像(3.8版本稳定)

2.1.2 镜像加载(拉取困难时)

若因网络问题无法从 Docker Hub 拉取镜像,可使用课前资料提供的本地镜像文件,通过以下命令加载:

1
2
# 假设镜像文件名为 rabbitmq-3.8-management.tar
docker load -i rabbitmq-3.8-management.tar

2.1.3 端口说明

RabbitMQ 启动后会占用两个核心端口,用途如下:

  • 15672:管理控制台端口,通过浏览器访问(如 http://服务器IP:15672),用于可视化管理交换机、队列、用户等;
  • 5672:AMQP 协议通信端口,微服务(如支付服务、交易服务)通过该端口与 RabbitMQ 通信(发送 / 接收消息)。

2.1.4 访问管理控制台

  1. 打开浏览器,输入地址 http://服务器IP:15672(如本地部署则为 http://localhost:15672);
  2. 输入用户名 itheima 和密码 123321 登录;
  3. 登录后进入 “总览页面”,可查看 RabbitMQ 的节点状态、连接数、交换机 / 队列数量等核心信息。

2.1.5 RabbitMQ 核心架构

RabbitMQ 的架构包含 5 个核心组件,各组件职责明确:

  1. 生产者(Publisher:消息的发送方,如支付服务在支付成功后向 RabbitMQ 发送 “支付成功” 消息;
  2. 消费者(Consumer):消息的接收方,如交易服务监听并接收 “支付成功” 消息,用于更新订单状态;
  3. 队列(Queue):消息的暂存容器,属于 “点对点” 存储(一个队列可被多个消费者监听,但一条消息仅会被一个消费者消费),支持消息持久化(重启 RabbitMQ 后消息不丢失);
  4. 交换机(Exchange):消息的路由中心,生产者发送的消息首先到达交换机,由交换机根据 “路由规则” 将消息转发到对应的队列;交换机本身不存储消息,若没有匹配的队列,消息会丢失;
  5. 虚拟主机(Virtual Host):用于实现 “数据隔离”,每个虚拟主机拥有独立的交换机、队列、用户权限,不同虚拟主机间的数据互不干扰(如项目 A 和项目 B 使用同一 RabbitMQ 集群,但通过不同虚拟主机隔离数据)。

2.2 收发消息(基于管理控制台)

通过 RabbitMQ 管理控制台,可直观地体验 “发送消息 - 路由消息 - 接收消息” 的完整流程,核心是理解 “交换机 - 队列 - 绑定关系” 的协作机制。

2.2.1 交换机(Exchange):消息路由中心

交换机的核心作用是 “根据路由规则将消息转发到队列”,本身不存储消息。

步骤 1:查看默认交换机

登录管理控制台后,点击顶部菜单栏的 Exchanges 选项卡,可看到 RabbitMQ 自带的 7 个默认交换机,例如:

  • amq.fanout:扇形交换机(Fanout Exchange),属于 “广播型” 交换机,会将消息转发到所有与它绑定的队列(不依赖路由键);
  • amq.direct:直连交换机(Direct Exchange),根据 “路由键(Routing Key)” 精确匹配队列;
  • amq.topic:主题交换机(Topic Exchange),根据 “路由键模式” 模糊匹配队列。
步骤 2:通过交换机发送消息(无绑定队列时)

amq.fanout 交换机为例,测试消息发送:

  1. 点击 amq.fanout 进入交换机详情页;
  2. 下拉到 “Publish message” 区域,填写消息内容:
    • Payload:消息体(如 {"orderId":"123456","status":"paid","amount":100});
    • 其他字段(如 Routing Key、Headers)保持默认;
  3. 点击 Publish message 发送消息。

此时,由于 amq.fanout 未与任何队列绑定,消息会直接丢失(交换机无存储能力),这验证了 “交换机不存储消息,仅负责路由” 的特性。

2.2.2 队列(Queue):消息暂存容器

队列是 RabbitMQ 中唯一存储消息的组件,需手动创建,且需与交换机绑定后才能接收消息。

步骤 1:创建队列
  1. 点击顶部菜单栏的 Queues 选项卡,点击右侧的 Add a new queue 按钮;
  2. 填写队列配置(以创建 hello.queue1 为例):
    • Name:队列名称(如 hello.queue1,需唯一);
    • Durability:是否持久化(选择 Durable,表示 RabbitMQ 重启后队列不丢失);
    • Auto delete:是否自动删除(选择 No,表示手动删除前队列一直存在);
    • 其他字段(如 Exclusive、Arguments)保持默认;
  3. 点击 Add queue 完成创建。
步骤 2:创建第二个队列

重复上述步骤,创建第二个队列 hello.queue2(配置与 hello.queue1 一致),最终在 Queues 列表中可看到两个新建的队列。

2.2.3 绑定关系(Binding):连接交换机与队列

交换机与队列不会自动关联,需通过 “绑定关系” 明确两者的关联规则(如路由键匹配规则)。

步骤 1:绑定队列到交换机

以将 hello.queue1hello.queue2 绑定到 amq.fanout 交换机为例:

  1. 回到 Exchanges 选项卡,点击 amq.fanout 交换机 交换机进入详情页;
  2. 下拉到 “Bindings” 区域,点击 Add binding from this exchange 按钮;
  3. 填写绑定配置(以绑定 hello.queue1 为例):
    • To queue:选择目标队列 hello.queue1
    • amq.fanout 是扇形交换机,无需配置 Routing Key(留空即可);
  4. 点击 Bind 完成绑定;
  5. 重复步骤 2-4,将 hello.queue2 也绑定到 amq.fanout 交换机。
步骤 2:查看绑定结果

绑定完成后,在 amq.fanout 交换机的 “Bindings” 列表中,可看到两条绑定记录,分别对应 hello.queue1hello.queue2,说明交换机与队列已成功关联。

2.2.4 发送与接收消息(完整流程)

完成交换机、队列、绑定关系的配置后,即可测试完整的消息收发流程:

步骤 1:发送消息到交换机
  1. amq.fanout 交换机详情页,再次进入 “Publish message” 区域;
  2. 填写消息体(如 {"msg":"Hello RabbitMQ!"}),点击 Publish message 发送。
步骤 2:查看队列中的消息
  1. 回到 Queues 选项卡,可发现 hello.queue1hello.queue2 的 “Ready” 列均显示为 1,说明各队列均收到 1 条消息(因 amq.fanout 是广播交换机,会将消息转发到所有绑定的队列);
  2. 点击 hello.queue1 进入队列详情页,下拉到 “Get messages” 区域,点击 Get message(s) 按钮,即可查看队列中存储的消息内容(包含消息体、发送时间、交换机等信息)。
步骤 3:消费消息(模拟消费者行为)

在 “Get messages” 区域,勾选 Ack modeAuto-ack(自动确认),再次点击 Get message(s),消息会被 “消费” 并从队列中移除(“Ready” 列变为 0),模拟了消费者接收并处理消息的过程。

通过以上步骤,可直观理解 RabbitMQ 的核心流程:生产者发送消息到交换机 → 交换机根据绑定关系将消息路由到队列 → 消费者从队列中获取并消费消息

2.3 数据隔离(Virtual Host 与用户管理)

在实际生产环境中,一台 RabbitMQ 服务器可能被多个项目共享(如公司内部的电商项目、ERP 项目等)。为避免不同项目的消息数据互相干扰,需通过 “虚拟主机(Virtual Host)” 和 “用户管理” 实现数据隔离。

2.3.1 用户管理(权限控制)

RabbitMQ 的用户用于登录管理控制台和访问虚拟主机,不同用户可配置不同的权限(如仅允许访问特定虚拟主机)。

步骤 1:查看现有用户

点击顶部菜单栏的 Admin 选项卡,在 “Users” 列表中可看到当前所有用户(默认仅有安装时创建的 itheima 用户)。用户信息包含:

  • Name:用户名(如 itheima);
  • Tags:用户角色(administrator 表示超级管理员,拥有所有操作权限);
  • Can access virtual host:用户可访问的虚拟主机(itheima 默认可访问 / 虚拟主机)。
步骤 2:创建新项目用户

以创建 “黑马商城” 项目的专用用户 hmall 为例:

  1. 在 Admin 选项卡的 “Add a user” 区域,填写用户信息:
    • Usernamehmall(用户名);
    • Passwordhmall123(密码);
    • Tags:选择 none(普通用户,仅拥有被授权的权限);
  2. 点击 Add user 完成创建。

此时,新用户 hmall 尚未被授权访问任何虚拟主机(在 “Can access virtual host” 列显示为 “none”),无法操作任何资源。

2.3.2 虚拟主机(数据隔离)

虚拟主机是 RabbitMQ 实现数据隔离的核心机制,每个虚拟主机拥有独立的交换机、队列和权限配置,不同虚拟主机间的数据完全隔离。

步骤 1:创建虚拟主机
  1. 在 Admin 选项卡的 “Virtual Hosts” 区域,点击 Add a new virtual host 按钮;
  2. 填写虚拟主机名称(如 /hmall,建议以项目名作为标识),点击 Add virtual host 完成创建。
步骤 2:授权用户访问虚拟主机

hmall 用户授权访问 /hmall 虚拟主机:

  1. 在 “Users” 列表中,点击 hmall 用户名进入用户详情页;
  2. 在 “Permissions” 区域的 “Virtual Host” 下拉框中,选择 /hmall
  3. 点击 Set permission 按钮,授予 hmall 用户对 /hmall 虚拟主机的所有操作权限(默认权限配置可满足大多数场景)。

授权后,hmall 用户的 “Can access virtual host” 列会显示 /hmall,表示该用户仅能操作 /hmall 虚拟主机内的资源,与其他虚拟主机(如默认的 /)完全隔离。

2.3.3 数据隔离的意义

通过 “用户 + 虚拟主机” 的组合,可实现以下隔离效果:

  • 项目隔离:电商项目使用 /hmall 虚拟主机,ERP 项目使用 /erp 虚拟主机,两者的交换机、队列数据互不干扰;
  • 权限控制hmall 用户仅能操作 /hmall 虚拟主机,无法访问 /erp 虚拟主机的资源,避免误操作;
  • 资源统计:可针对虚拟主机单独统计消息量、吞吐量等指标,便于项目级别的监控与优化。

综上,RabbitMQ 通过灵活的用户管理和虚拟主机机制,有效解决了多项目共享 MQ 服务器时的数据隔离问题,是生产环境中必须掌握的配置技巧。

思考

如何在 RabbitMQ 中进行消息的发布和订阅?

除了同步调用,微服务架构中还有哪些调用方式?

怎样配置和管理 RabbitMQ 的用户和权限?