MQ 选型与架构
什么时候该用消息队列(异步/解耦/削峰/数据流)、RabbitMQ vs RocketMQ vs Kafka 对比,以及引入 MQ 带来的复杂度代价。
什么时候使用消息队列
企业引入 MQ 不是为了凑技术栈,而是为了解决以下四大核心痛点:
1. 异步处理(Asynchrony)
- 场景:用户在电商系统下单成功后,需要发送短信通知、赠送积分、生成发票、通知仓库配货。
- 不用 MQ:用户点击"提交订单"后,后端同步调用短信接口(2秒)→ 积分接口(1秒)→ 发票接口(2秒)……用户至少卡顿 5 秒以上,体验极差。
- 使用 MQ:订单核心逻辑处理完(约 20ms)后,往 MQ 发一条"订单已创建"的消息,直接给用户返回"下单成功"。短信、积分等下游服务异步消费这条消息,各忙各的,互不阻塞。
2. 系统解耦(Decoupling)
- 场景:订单系统需要调用物流系统。
- 不用 MQ:订单系统代码里必须硬编码引入物流系统的 RPC/HTTP 接口。如果物流系统今天升级、宕机或者接口改名,订单系统就会直接报错,甚至跟着一起挂掉。
- 使用 MQ:订单系统只管往 MQ 扔"订单已支付"消息,它不需要知道物流系统是否存在。物流系统自己订阅这个 Topic 即可。即使物流系统挂了一整天,只要 MQ 还活着,订单系统就照常运行。
3. 流量削峰(Peak Shaving)
- 场景:秒杀、抢购、早晚高峰通知推送等突发流量。
- 不用 MQ:瞬间涌入的 10 万次请求直接打到 MySQL,数据库连接池瞬间爆炸,整个系统瘫痪。
- 使用 MQ:前端涌入的请求先全部进入 MQ。后端微服务按照自己的最大处理能力(比如每秒 1000 条)从容地从 MQ 拉取消息。MQ 就像一个蓄水池,把洪峰变成平缓的流水。
4. 大数据与日志流处理(Data Streaming)
- 场景:收集所有微服务的业务日志、用户点击行为数据,灌入 Elasticsearch 或大数据平台(Flink/Spark)。
- 使用 MQ:这种高吞吐、允许极小概率丢失但绝对不能阻塞业务链路的数据流,天然适合 MQ(尤其是 Kafka)。
MQ 对比
| 特性 / 维度 | RabbitMQ | RocketMQ(阿里开源) | Kafka |
|---|---|---|---|
| 单机吞吐量 | 万级(最弱) | 十万级(高) | 百万级(极高) |
| 时效性 | 微秒级(延迟极低) | 毫秒级 | 毫秒级 |
| 可用性 | 主从架构 | 分布式架构(极高) | 分布式架构(集群高可用) |
| 功能特性 | 路由功能极强,生态健全 | 拥有分布式事务、延迟队列、死信队列等丰富的电商特性 | 功能聚焦,核心优势是高性能顺序读写和日志吞吐 |
| 核心业务场景 | 传统企业、金融级系统(对路由和延迟要求高,并发量适中) | 电商、大厂核心业务(需要严格的分布式事务和复杂业务逻辑) | 大数据、日志收集、监控流(追求极致吞吐量和高并发) |
使用场景 / 架构建议
以下场景不建议使用 MQ:
- 强同步、强一致性场景:如果前端发起一个请求必须立刻拿到结果(如查询银行卡余额、修改密码后立即验证),使用 MQ 是灾难——MQ 保证的是最终一致性,而不是强一致性。
- 简单的单体 CRUD 系统:如果只是访问量不大的内部管理后台(Monolith),直接开 Goroutine 异步处理即可,引入 MQ 是画蛇添足。
- 资源极度受限的环境:如果服务器只有 2核4G,运行业务代码已经捉襟见肘,再塞一个吃内存和磁盘 I/O 的 Kafka 进去,服务器会被直接拖垮。
引入 MQ 会带来额外的负担吗?
会。架构圈有一句话:“引入中间件是为了解决老问题,但它一定会带来新问题。”
引入 MQ 的"系统复杂度税"包括:
1. 系统可用性降低
原本你的系统只需保证「业务代码 + 数据库」活着就行。现在多了一个 MQ,一旦 MQ 挂了(磁盘满、集群网络分区等),整个链路直接瘫痪。你不得不去搞 MQ 的集群、容灾,运维成本大幅上升。
2. 消息丢失与重复消费(幂等性)
- 网络抖动可能导致生产者以为消息没发成功而重发;或者消费者处理完了但 ACK 没发回去,导致消息重复投递。
- 应对:消费端代码必须实现幂等性——同一条消息消费一次和消费一百次,结果必须一致。常用手段包括数据库唯一索引、Redis 去重等。
3. 分布式事务与一致性问题
原本在同一个数据库里用一个事务就能搞定的事,现在拆成了:订单扣钱 → MQ 通知库存 → 库存扣减。如果库存扣减失败了怎么办?
- 应对:必须设计补偿机制(TCC、本地消息表、Saga 等),或者使用支持分布式事务的 MQ(如 RocketMQ),来保证数据最终一致性。
4. 消息顺序性问题
多个消费者并发读取时,如何保证"创建订单 → 支付订单 → 发货订单"不会因为网络延迟变成"发货 → 创建 → 支付"?
- 应对:通过合理设计消息 Key,将需要保证顺序的消息路由到同一个 Partition。