134 lines
6.0 KiB
Markdown
134 lines
6.0 KiB
Markdown
---
|
||
tags:
|
||
- MQ
|
||
create time: 2026-05-24 19:52
|
||
---
|
||
|
||
# MQ 与微服务
|
||
|
||
## 概述
|
||
|
||
MQ 是微服务架构中实现服务间异步通信、解耦和削峰的核心组件。本文探讨同步与异步通信的取舍、事件驱动架构、服务网格对 MQ 的影响、背压传播机制以及 Saga 在微服务中的实践。
|
||
|
||
## 正文
|
||
|
||
### 微服务间通信:同步 vs 异步
|
||
|
||
微服务之间的通信本质上是两种模式的博弈:
|
||
|
||
**同步通信(HTTP/gRPC)**:调用方发起请求,阻塞等待响应。优点是简单直观,适合查询类操作和需要即时响应的场景。缺点是强依赖——被调用方不可用时,调用方也会失败。
|
||
|
||
**异步通信(MQ)**:发送方发出消息后立即返回,不关心消费方何时处理。优点是解耦、削峰、提高系统弹性。缺点是增加了复杂度,调试困难,且不适合需要即时响应的场景。
|
||
|
||
选择原则很简单:**需要即时响应用同步,不需要即时响应用异步**。一个典型的电商系统,查询商品详情用 gRPC,下单后发扣库存消息用 MQ。
|
||
|
||
> [!question]
|
||
> 一个用户注册流程需要:创建账号、发欢迎邮件、初始化用户配置。这三步应该用同步还是异步?如果邮件发送失败怎么办?
|
||
|
||
### 事件驱动微服务
|
||
|
||
事件驱动架构(EDA)是异步通信的极致形态。在 EDA 中,每个服务通过**领域事件**与其他服务交互,没有直接的 API 调用依赖。
|
||
|
||
```mermaid
|
||
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 处理不过来** → **数据库连接池耗尽** → **数据库响应变慢** → **更多请求超时** → **系统雪崩**
|
||
|
||
端到端的背压管理需要每一层都参与:
|
||
|
||
```go
|
||
// 带背压控制的消费者
|
||
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 还有什么解法?
|
||
|
||
生产实践中,大多数团队会混合使用两种方式:核心业务流程用编排式(可控制),辅助流程用协同式(松耦合)。
|
||
|
||
## 关联笔记
|
||
|
||
- [[44-MQ-高可用架构]]
|
||
- [[46-MQ-分布式事务实践]]
|
||
- [[48-MQ-客户端-SDK-最佳实践]]
|
||
- [[49-MQ-客户端连接管理]]
|