Lesson 0007 · 系统设计 · Phase 2 数据层

消息队列与异步化

⏱ 预计 30 分钟 🎯 削峰、解耦、以及「至少一次」的代价 📖 前置:Lesson 0005 · Lesson 0006
与你的 Mission 的关系

消息队列(Message Queue)是大型系统的「传送带」:秒杀(Lesson 0012)、通知系统(Lesson 0014)、feed 流(Lesson 0011)全都建立在它上面。面试深挖必问的投递语义与幂等,恰好也是工作中 MQ 事故的头号来源。

§1 · 先问一句哪些步骤必须同步?

异步化的第一步不是引入 MQ,而是切开「用户在等」和「用户不等」。以注册流程为例:

步骤同步/异步理由
校验 + 创建账号 + 发 token同步用户在等结果,后续操作依赖它
发欢迎邮件异步晚 30 秒无所谓,失败也不该阻塞注册
初始化推荐画像异步后台任务,跟用户眼前的体验无关
记营销埋点异步纯旁路,丢了都能接受

把「用户不等」的步骤扔进队列,注册接口的耗时和稳定性立即改善。这就是 MQ 的第一价值:把慢的、脆的、跟主流程无关的东西摘出去

§2 · MQ 的三大价值

§3 · 代价投递语义与幂等

天下没有免费的队列。三种投递语义(Kafka 文档的经典划分[1]):

语义含义代价
至多一次 at-most-once可能丢,绝不重消费前崩溃 → 消息没了(适合埋点等可丢场景)
至少一次 at-least-once绝不丢,可能重消费者必须幂等(工业界默认选择)
精确一次 exactly-once不丢不重性能代价大、条件苛刻(Kafka 事务 + 幂等生产者)
面试与工作的头号考点

「至少一次」意味着重复消费是常态而非异常(消费者处理完还没提交 offset 就崩溃,重启后重收同一条)。所以消费者必须幂等idempotent):同一条消息处理 100 遍,效果跟 1 遍一样。标准手段:消息带唯一 ID,消费前查去重表;或数据库写入用「INSERT ... ON CONFLICT」类幂等写法。Stripe 的幂等 API 设计是绝佳范本[2]

另外三个要知道的坑

反模式:什么都往 MQ 里塞

MQ 的代价:排查链路变长、最终一致引入心智负担、运维一套新系统。同步能解决且规模可预期的,不要上 MQ。面试中主动说「这个场景我不建议用 MQ,因为……」是高级信号。

§4 · 随堂检测

§5 · 检索练习

凭记忆做两件事:① 把「支付成功」后的流程拆成同步/异步两列(至少 5 个步骤);② 写出「至少一次」语义下消费者保证幂等的两种手段。

核对参考答案

① 同步:更新订单状态、扣减库存确认;异步:通知(短信/推送)、积分发放、物流创建、报表更新、营销触达。② 消息唯一 ID + 去重表(消费前查);幂等数据库写(upsert / 条件更新 / 唯一索引兜底)。

§6 · 本周行动

行动任务(约 30 分钟)

① 在你公司系统里找一个「本可异步却在同步链路里拖着接口变慢」的步骤(发通知、写日志、同步第三方是高发区);
② 如果已有 MQ:消费者是幂等的吗?lag 监控在哪看?有没有配 DLQ?
③ 反过来找一个「明明该同步却扔进 MQ」的步骤——让用户无感知等待变成不确定等待。

§7 · 延伸资源

💬 想一起把你公司的某个同步链路改成异步设计?把流程发我。