Skip to content

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 ZSetscore=到期时间戳,轮询取到期任务轻量通用
定时任务轮询定时扫"到期未支付"简单兜底

对"超时未支付"这类,轮询 + 状态机往往比精确延迟更可靠,且天然幂等。


自检清单 ​

  • [ ] 能按"是否等待结果"判断要不要异步
  • [ ] 能说清几种 MQ 的适用边界,会做选型
  • [ ] 理解"至少一次 + 消费幂等"是主流组合
  • [ ] 会给消费者做幂等去重和失败重试
  • [ ] 知道延迟队列的几种实现思路

常见误区 ​

  • 误区:MQ 是万能的解耦神器。 引入 MQ 就引入可靠性问题,先问"真的需要异步吗"。
  • 误区:MQ 不会丢消息。 默认"至少一次",会重复,消费方必须幂等。
  • 误区:延迟队列只能靠 MQ 延迟消息。 轮询 + 状态机对"超时未支付"更可靠。
  • 误区:同步改异步就快。 快的是"响应"不是"总耗时",总量一样。

动手练习 ​

练习 A:下单后发通知 ​

用 Redis Stream 实现"下单 → 异步发通知",接口立即返回,消费方异步处理。

练习 B:消费幂等 ​

给消费者加业务键去重,用重复消息验证只处理一次。

练习 C:超时未支付 ​

用 Redis ZSet 或轮询实现"下单 30 分钟未支付自动取消",并验证到期行为。


相关文档 ​

本区 ​

其他域 ​