06 · 异步与消息:消息队列 / 事件驱动
下单后发通知、注册后发欢迎信、库存扣减同步积分——这些"不要求立即完成"的事,后端用消息队列异步化。本篇讲选型、事件驱动、消费幂等。
速览卡
| 项 | 内容 |
|---|---|
| 一句话定位 | 什么时候该异步、MQ 怎么选、事件驱动怎么落地、消费怎么不出错 |
| 核心内容 | 同步 vs 异步、MQ 选型、事件驱动架构、消费幂等、延迟队列、投递保证 |
| 读完能回答 | 什么时候该用 MQ?消息会丢吗?重复消费怎么办?延迟队列怎么实现? |
| 相关板块 | C04 · 一致性与事务 | C05 · 服务架构 | C15 · 定时任务 |
一、什么时候该异步
判据:调用方"不等待结果也能正确",且慢操作拖慢主流程时。
| 场景 | 同步 | 异步 |
|---|---|---|
| 下单必须立刻知道是否成功 | ✅ | — |
| 下单后发短信通知 | — | ✅(可以后到) |
| 用户上传后做视频转码 | — | ✅(几分钟后完成) |
| 计算积分 | — | ✅ |
好处:接口响应变快、削峰填谷、故障隔离(通知服务挂了不影响下单)。
二、MQ 选型
| 消息队列 | 特点 | 适用 |
|---|---|---|
| RabbitMQ | 功能全、路由灵活 | 中小规模、复杂路由 |
| Kafka | 高吞吐、顺序、持久 | 大数据量、事件流 |
| Redis Stream | 轻量、复用 Redis | 小项目够用 |
| 云消息服务 | 托管、免运维 | 上云首选 |
个人全栈推荐:小项目用 Redis Stream 或托管 MQ 起步,Kafka 等量大了再上(属于"了解即可"到"掌握"之间)。
三、事件驱动架构
3.1 从"调用"到"发布"
text
同步耦合: 下单服务 ──HTTP──▶ 通知服务
事件驱动: 下单服务 ──publish:order.created──▶ MQ ──▶ 通知服务
└──▶ 积分服务3.2 好处
- 解耦:下单服务不知道谁在听
- 扩展:新消费者(如加个反作弊)只需订阅,不改下单
- 削峰:洪峰积在 MQ,消费方匀速处理
类比前端:事件驱动 ≈ 前端的事件总线/发布订阅,只是跨了进程、还要保证可靠投递。
四、投递保证:至少一次 vs 精确一次
| 语义 | 含义 | 代价 |
|---|---|---|
| 至少一次(At-least-once) | 不丢,但可能重复 | 消费方必须幂等 |
| 最多一次 | 可能丢 | 简单但不可靠 |
| 精确一次 | 不丢不重 | 依赖 MQ 事务/去重,复杂 |
后端主流是"至少一次 + 消费幂等":接受重复投递,用业务键去重(见 C04)。
python
def consume_order_created(msg):
# 幂等:同一 order_id 只处理一次
if redis.set(f"processed:{msg.order_id}", "1", nx=True, ex=86400):
grant_points(msg.order_id)五、消费幂等与重试
| 环节 | 保证 |
|---|---|
| 消费失败 | 重试 N 次,进死信队列 |
| 重复消费 | 业务键去重 |
| 部分成功 | 状态机 + 补偿 |
套路:消费 → 幂等去重 → 业务处理 → 记录进度;失败重试,多次失败进死信让人工介入。
六、延迟队列
"下单后 30 分钟未支付自动取消"这类需求 = 延迟队列。
| 实现 | 思路 | 适用 |
|---|---|---|
| MQ 延迟消息 | 消息指定延迟时间 | RabbitMQ/云 MQ 支持 |
| Redis ZSet | score=到期时间戳,轮询取到期任务 | 轻量通用 |
| 定时任务轮询 | 定时扫"到期未支付" | 简单兜底 |
对"超时未支付"这类,轮询 + 状态机往往比精确延迟更可靠,且天然幂等。
自检清单
- [ ] 能按"是否等待结果"判断要不要异步
- [ ] 能说清几种 MQ 的适用边界,会做选型
- [ ] 理解"至少一次 + 消费幂等"是主流组合
- [ ] 会给消费者做幂等去重和失败重试
- [ ] 知道延迟队列的几种实现思路
常见误区
- 误区:MQ 是万能的解耦神器。 引入 MQ 就引入可靠性问题,先问"真的需要异步吗"。
- 误区:MQ 不会丢消息。 默认"至少一次",会重复,消费方必须幂等。
- 误区:延迟队列只能靠 MQ 延迟消息。 轮询 + 状态机对"超时未支付"更可靠。
- 误区:同步改异步就快。 快的是"响应"不是"总耗时",总量一样。
动手练习
练习 A:下单后发通知
用 Redis Stream 实现"下单 → 异步发通知",接口立即返回,消费方异步处理。
练习 B:消费幂等
给消费者加业务键去重,用重复消息验证只处理一次。
练习 C:超时未支付
用 Redis ZSet 或轮询实现"下单 30 分钟未支付自动取消",并验证到期行为。
相关文档
本区
- 04 · 一致性与事务 —— 幂等与最终一致
- 05 · 服务架构 —— 服务间通信
- 13 · 缓存与 Redis —— Redis Stream/ZSet
- 15 · 定时任务 —— 轮询与延迟
其他域
- 05 · 工程化与 LLMOps —— 任务异步化在 AI 场景