Files
cs-note/hhs/MQ/01-基础概念/2-MQ-适用场景与选型原则.md
T

238 lines
8.5 KiB
Markdown
Raw Normal View History

2026-05-24 20:51:06 +08:00
---
tags:
- MQ
2026-05-24 20:58:10 +08:00
- 架构
- 异步
- 选型
- 流量削峰
2026-05-24 20:51:06 +08:00
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 或长连接更合适。
2026-05-24 20:58:10 +08:00
### 你需要 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。
2026-05-24 20:51:06 +08:00
### 选型决策框架
2026-05-24 20:58:10 +08:00
确认需要 MQ 后,选型时需要综合考虑以下维度。没有"最好"的 MQ,只有"最合适"的:
2026-05-24 20:51:06 +08:00
| 维度 | Kafka | RabbitMQ | RocketMQ | Pulsar |
|------|-------|----------|----------|--------|
| 吞吐量 | 极高(百万级/s) | 中等(万级/s) | 高(十万级/s) | 极高(百万级/s) |
| 延迟 | 毫秒~秒级 | 微秒~毫秒级 | 毫秒级 | 毫秒级 |
| 消息可靠性 | 高(副本机制) | 高(镜像队列) | 高(同步双写) | 高(BookKeeper) |
| 运维成本 | 中等(依赖 ZK/KRaft) | 低(开箱即用) | 中等 | 高(组件多) |
| 生态 | 极丰富(Flink/Spark) | 成熟(插件体系) | 较丰富 | 成长中 |
| 典型场景 | 大数据管道、日志 | 企业应用、复杂路由 | 电商、金融 | 多租户、云原生 |
2026-05-24 20:58:10 +08:00
下面是基于核心需求的快速决策路径:
```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-选型对比]]。
2026-05-24 20:51:06 +08:00
> [!question]
> 如果你的团队只有 3 个人,系统日均消息量 10 万条,你会选择哪个 MQ?为什么?
### 反模式警示
**MQ 万能论**
"所有异步操作都用 MQ"是一个常见误区。如果只是简单的后台任务,用一个 goroutine 池或者任务调度器就够了,不需要引入一个分布式 Broker。MQ 的运维成本、消息丢失风险、幂等性处理等都是实打实的工程代价。
**过度异步化**
把本该同步完成的核心链路也异步化,会导致:
- 事务边界变得模糊,难以保证数据一致性
- 排查问题时调用链断裂,日志追踪困难
- 用户反馈"下单成功了但库存没扣"——因为扣库存的消息还在队列里排队
记住一个原则:**用户需要立即看到结果的操作,不要异步化。**
2026-05-24 20:58:10 +08:00
**忽略幂等设计**
这是引入 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-消息幂等性]]。
2026-05-24 20:51:06 +08:00
## 关联笔记
2026-05-24 20:58:10 +08:00
- [[1-MQ-基础概念]] — MQ 是什么、核心术语、Pull/Push 模型
- [[3-MQ-消息模型]] — 队列与发布订阅模型的深入对比
- [[27-MQ-选型对比]] — 七大维度横向对比各主流 MQ
- [[14-MQ-消息幂等性]] — 幂等消费的设计方案
- [[12-MQ-消息确认与持久化]] — ACK 机制如何保证消息不丢
- [[16-MQ-死信队列与消息回溯]] — 消费失败后的兜底处理
- [[46-MQ-分布式事务实践]] — 事务消息在电商/金融场景的实战