Files
cs-note/hhs/MQ/01-基础概念/2-MQ-适用场景与选型原则.md
T
2026-05-24 20:58:10 +08:00

238 lines
8.5 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
- 架构
- 异步
- 选型
- 流量削峰
create time: 2026-05-24 19:52
---
# MQ 适用场景与选型原则
## 概述
MQ 并非银弹。本文详解三大经典适用场景的架构实践,明确不适合使用 MQ 的边界,并给出选型决策框架和常见反模式警示。
## 正文
### 三大经典场景详解
#### 场景一:系统解耦
当多个下游系统需要感知同一事件时,MQ 让生产者无需感知消费者的存在。
```mermaid
graph LR
O["Order Service"] -->|Publish| MQ["MQ Broker"]
MQ -->|Subscribe| INV["Inventory Service"]
MQ -->|Subscribe| PTS["Points Service"]
MQ -->|Subscribe| LOG["Logistics Service"]
MQ -->|Subscribe| NOTIFY["Notification Service"]
```
新增"数据仓库"服务?只需要订阅 `order_created` topic,Order Service 的代码一行不用动。
```go
// Order Service 只管发消息
func CreateOrder(order Order) {
db.Save(order)
mq.Publish("order_created", order)
}
// Inventory Service 独立订阅
func HandleOrderCreated(msg Message) {
var order Order
json.Unmarshal(msg.Body, &order)
inventory.Deduct(order.ItemID, order.Qty)
}
```
#### 场景二:异步处理
将非核心链路从主流程中剥离,缩短用户感知的响应时间。
```mermaid
graph TD
U["User Request"] --> API["API Gateway"]
API --> DB["Core DB Write"]
API -->|Async| MQ["MQ Broker"]
MQ --> EMAIL["Email Service"]
MQ --> ANALYTICS["Analytics Service"]
MQ --> SEARCH["Search Index Update"]
```
以电商下单为例,核心链路是扣库存和创建订单记录(同步),而发邮件、更新搜索索引、埋点统计等可以异步处理。
```go
func PlaceOrder(order Order) {
// 同步: 核心链路
db.CreateOrder(order)
db.DeductStock(order.ItemID, order.Qty)
// 异步: 非核心链路,扔给 MQ
mq.Publish("order_placed", order) // 邮件、搜索索引、埋点各自消费
}
```
> [!question]
> 如果异步任务失败了怎么办?比如邮件发送失败,用户收不到通知——你需要什么机制来保证最终成功?
#### 场景三:流量削峰
秒杀、抢购等场景下,瞬时流量远超系统承载能力。MQ 作为缓冲层,让下游匀速处理。
```mermaid
graph LR
REQ["10000 QPS"] --> LB["Load Balancer"]
LB --> APP["App Server"]
APP -->|Instant peak| MQ["MQ Buffer"]
MQ -->|Rate limited| DB["Database"]
```
```go
// 生产者: 接住流量
func Seckill(ctx context.Context, req SeckillReq) error {
// 先做前置校验(限流、参数校验)
if !rateLimiter.Allow() {
return ErrTooManyRequests
}
return mq.Publish("seckill", req)
}
// 消费者: 匀速消费,保护数据库
func SeckillConsumer() {
for msg := range mq.Subscribe("seckill") {
// 限速: 每秒最多处理 500 个请求
rateLimiter.Wait()
processSeckill(msg)
}
}
```
> [!question]
> 削峰场景中,如果 MQ 的消费速度持续低于生产速度,会发生什么?如何应对?
### 不适合使用 MQ 的场景
MQ 解决了分布式系统中的很多问题,但有些场景引入 MQ 反而画蛇添足:
**1. 强一致性要求**
金融转账、扣款等场景要求操作原子性。MQ 的异步特性天然支持最终一致性,但无法保证强一致。如果 A 扣款成功但消息丢失,B 没收到钱——这就是事故。
**2. 简单的 RPC 调用**
如果 A 服务调用 B 服务,只需要一个同步返回值(比如查询用户信息),直接 HTTP/gRPC 调用更简单。引入 MQ 多了一层 Broker,增加了延迟和运维成本,纯属多此一举。
**3. 实时性极高的场景**
游戏服务器的实时状态同步、在线聊天等场景要求毫秒级响应。MQ 的存储转发机制会引入额外延迟,直接 WebSocket 或长连接更合适。
### 你需要 MQ 吗?——决策引导
在进入选型细节之前,先回答一个更根本的问题:**你的系统是否真的需要 MQ?**
很多团队在"技术潮流"的驱动下盲目引入 MQ,最后发现自己在用大炮打蚊子。下面这个流程图帮你快速判断:
```mermaid
graph TD
START["你有异步通信需求吗?"] -->|否| NO["不需要 MQ,直接调用即可"]
START -->|是| Q1{"需要解耦多个下游?"}
Q1 -->|是| YES["建议引入 MQ"]
Q1 -->|否| Q2{"有流量突增需要削峰?"}
Q2 -->|是| YES
Q2 -->|否| Q3{"有非核心链路可异步化?"}
Q3 -->|是| YES
Q3 -->|否| Q4{"需要消息持久化和重试?"}
Q4 -->|是| MAYBE["考虑 MQ 或轻量方案"]
Q4 -->|否| SIMPLE["考虑 goroutine 池或任务队列"]
style YES fill:#27AE60,color:#fff
style NO fill:#95A5A6,color:#fff
style MAYBE fill:#F39C12,color:#fff
style SIMPLE fill:#3498DB,color:#fff
```
> [!warning]
> 不要因为"别人在用"就引入 MQ。如果你的需求只是简单的后台任务,一个 goroutine 池 + channel 就能搞定,不需要动用分布式 Broker。
### 选型决策框架
确认需要 MQ 后,选型时需要综合考虑以下维度。没有"最好"的 MQ,只有"最合适"的:
| 维度 | Kafka | RabbitMQ | RocketMQ | Pulsar |
|------|-------|----------|----------|--------|
| 吞吐量 | 极高(百万级/s) | 中等(万级/s) | 高(十万级/s) | 极高(百万级/s) |
| 延迟 | 毫秒~秒级 | 微秒~毫秒级 | 毫秒级 | 毫秒级 |
| 消息可靠性 | 高(副本机制) | 高(镜像队列) | 高(同步双写) | 高(BookKeeper) |
| 运维成本 | 中等(依赖 ZK/KRaft) | 低(开箱即用) | 中等 | 高(组件多) |
| 生态 | 极丰富(Flink/Spark) | 成熟(插件体系) | 较丰富 | 成长中 |
| 典型场景 | 大数据管道、日志 | 企业应用、复杂路由 | 电商、金融 | 多租户、云原生 |
下面是基于核心需求的快速决策路径:
```mermaid
graph TD
START["确认需要 MQ"] --> Q1{"核心需求是什么?"}
Q1 -->|"大数据 / 日志管道"| Kafka["Kafka"]
Q1 -->|"灵活路由 / 企业集成"| RabbitMQ["RabbitMQ"]
Q1 -->|"事务消息 / 电商金融"| RocketMQ["RocketMQ"]
Q1 -->|"多租户 / 云原生"| Pulsar["Pulsar"]
Q1 -->|"极致轻量 / 低延迟"| NATS["NATS"]
```
> [!tip]
> 如果你拿不准,**RabbitMQ 是最稳妥的起步选择**——运维简单、文档完善、社区活跃。等业务规模增长后,再根据瓶颈点考虑迁移。更多维度的深度对比见 [[27-MQ-选型对比]]。
> [!question]
> 如果你的团队只有 3 个人,系统日均消息量 10 万条,你会选择哪个 MQ?为什么?
### 反模式警示
**MQ 万能论**
"所有异步操作都用 MQ"是一个常见误区。如果只是简单的后台任务,用一个 goroutine 池或者任务调度器就够了,不需要引入一个分布式 Broker。MQ 的运维成本、消息丢失风险、幂等性处理等都是实打实的工程代价。
**过度异步化**
把本该同步完成的核心链路也异步化,会导致:
- 事务边界变得模糊,难以保证数据一致性
- 排查问题时调用链断裂,日志追踪困难
- 用户反馈"下单成功了但库存没扣"——因为扣库存的消息还在队列里排队
记住一个原则:**用户需要立即看到结果的操作,不要异步化。**
**忽略幂等设计**
这是引入 MQ 后最容易翻车的地方。网络抖动、消费者重启、Broker 重试都可能导致同一条消息被投递多次。如果消费者的处理逻辑不是幂等的,结果就是重复扣款、重复发货、重复积分。
```go
// 非幂等: 消息重投会导致重复扣款
func HandlePayment(msg Message) {
db.UpdateBalance(msg.UserID, -msg.Amount) // 每次执行都扣一次!
}
// 幂等: 利用消息 ID 做去重
func HandlePayment(msg Message) {
if db.Exists("processed:" + msg.ID) {
return // 已处理过,跳过
}
db.UpdateBalance(msg.UserID, -msg.Amount)
db.Set("processed:" + msg.ID, true)
}
```
> [!warning]
> 上 MQ 之前,先确保每个消费者都有幂等保障。幂等不是可选项,是必选项。详细设计见 [[14-MQ-消息幂等性]]。
## 关联笔记
- [[1-MQ-基础概念]] — MQ 是什么、核心术语、Pull/Push 模型
- [[3-MQ-消息模型]] — 队列与发布订阅模型的深入对比
- [[27-MQ-选型对比]] — 七大维度横向对比各主流 MQ
- [[14-MQ-消息幂等性]] — 幂等消费的设计方案
- [[12-MQ-消息确认与持久化]] — ACK 机制如何保证消息不丢
- [[16-MQ-死信队列与消息回溯]] — 消费失败后的兜底处理
- [[46-MQ-分布式事务实践]] — 事务消息在电商/金融场景的实战