Files
cs-note/hhs/MQ/12-架构与实战/47-MQ-与微服务.md
T
2026-05-24 20:51:06 +08:00

6.0 KiB
Raw Blame History

tags, create time
tags create time
MQ
2026-05-24 19:52

MQ 与微服务

概述

MQ 是微服务架构中实现服务间异步通信、解耦和削峰的核心组件。本文探讨同步与异步通信的取舍、事件驱动架构、服务网格对 MQ 的影响、背压传播机制以及 Saga 在微服务中的实践。

正文

微服务间通信:同步 vs 异步

微服务之间的通信本质上是两种模式的博弈:

同步通信(HTTP/gRPC):调用方发起请求,阻塞等待响应。优点是简单直观,适合查询类操作和需要即时响应的场景。缺点是强依赖——被调用方不可用时,调用方也会失败。

异步通信(MQ):发送方发出消息后立即返回,不关心消费方何时处理。优点是解耦、削峰、提高系统弹性。缺点是增加了复杂度,调试困难,且不适合需要即时响应的场景。

选择原则很简单:需要即时响应用同步,不需要即时响应用异步。一个典型的电商系统,查询商品详情用 gRPC,下单后发扣库存消息用 MQ。

[!question] 一个用户注册流程需要:创建账号、发欢迎邮件、初始化用户配置。这三步应该用同步还是异步?如果邮件发送失败怎么办?

事件驱动微服务

事件驱动架构(EDA)是异步通信的极致形态。在 EDA 中,每个服务通过领域事件与其他服务交互,没有直接的 API 调用依赖。

graph TD
    OS["订单服务"] -->|"OrderCreated"| MQ["MQ Broker"]
    MQ -->|"OrderCreated"| IS["库存服务"]
    MQ -->|"OrderCreated"| PS["支付服务"]
    MQ -->|"OrderCreated"| NS["通知服务"]
    IS -->|"InventoryReserved"| MQ
    MQ -->|"InventoryReserved"| PS
    PS -->|"PaymentCompleted"| MQ
    MQ -->|"PaymentCompleted"| OS
    MQ -->|"PaymentCompleted"| NS
    NS -->|"OrderCompleted"| MQ

EDA 的关键好处:

  • 松耦合:订单服务不需要知道有哪些服务消费了 OrderCreated 事件,新增消费者不需要改订单服务。
  • 可扩展:要加一个新的"积分服务",只需订阅 OrderCreated 事件,无需改动任何现有服务。
  • 弹性:某个消费者宕机,消息在 MQ 中堆积,恢复后继续处理,不影响生产者。

但 EDA 也有代价:调试困难(请求链路不直观)、数据一致性复杂(最终一致而非强一致)、事件风暴(一个事件触发一连串事件)。

服务发现与 MQ

微服务需要知道 MQ Broker 的地址。通常有几种方式:

  • 配置中心:将 Broker 地址放在 Nacos/Apollo 等配置中心,客户端动态获取。
  • 环境变量:简单直接,适合容器化部署。
  • DNS SRV 记录:通过 DNS 解析 Broker 地址,天然支持负载均衡。

多集群场景下,还需要路由策略——根据 Topic、地域或负载情况选择目标集群。

服务网格与 MQ

服务网格(Istio/Linkerd)通过 Sidecar 代理拦截服务间的网络流量,实现流量管理、安全和可观测性。但 MQ 的流量是否应该走服务网格?

答案通常是不走。原因:

  1. 协议不匹配:服务网格的 Sidecar 代理(Envoy)擅长处理 HTTP/gRPC,对 Kafka、RocketMQ 等自定义协议支持有限。
  2. 性能开销:每条消息都经过 Sidecar 代理会增加不必要的延迟。
  3. 长连接:MQ 客户端通常维护长连接,Sidecar 代理的连接管理可能与之冲突。

不过,mTLS(双向 TLS 认证)是可以应用于 MQ 流量的。可以在客户端 SDK 层面集成 mTLS,而不是依赖 Sidecar。

背压传播

背压(Backpressure)是分布式系统中经常被忽视的问题。当下游处理速度跟不上上游生产速度时,压力会沿链路反向传播:

MQ 堆积 → Consumer 处理不过来 → 数据库连接池耗尽 → 数据库响应变慢 → 更多请求超时 → 系统雪崩

端到端的背压管理需要每一层都参与:

// 带背压控制的消费者
func ConsumeWithBackpressure(consumer MQConsumer, db *sql.DB) {
    semaphore := make(chan struct{}, 100) // 最多 100 个并发处理

    for msg := range consumer.Messages() {
        semaphore <- struct{}{} // 获取信号量,满了就阻塞
        go func(m Message) {
            defer func() { <-semaphore }() // 释放信号量

            ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
            defer cancel()

            if err := processMessage(ctx, db, m); err != nil {
                // 处理失败,不 ACK,消息会重新投递
                consumer.Nack(m)
            } else {
                consumer.Ack(m)
            }
        }(msg)
    }
}

核心手段包括:限流(控制消费速率)、降级(非核心处理逻辑延后)、熔断(下游不可用时快速失败)。

Saga 在微服务中的实践

前面分布式事务篇提到的 Saga 模式,在微服务中有两种实现方式:

编排式 Saga(Orchestration):由一个 Saga 编排器(Orchestrator)集中控制流程。编排器通过 MQ 向各服务发送命令,等待执行结果,再决定下一步。

  • 优点:流程清晰,易于监控和调试。
  • 缺点:编排器成为单点和瓶颈。

协同式 Saga(Choreography):各服务通过事件自主协作,没有中心控制点。每个服务监听自己关心的事件,处理后发布新事件。

  • 优点:完全去中心化,服务间松耦合。
  • 缺点:流程分散在各服务中,难以理解和调试。

[!question] 微服务架构中,如果一个服务下线了,它订阅的事件会堆积。除了扩 Partition 还有什么解法?

生产实践中,大多数团队会混合使用两种方式:核心业务流程用编排式(可控制),辅助流程用协同式(松耦合)。

关联笔记