Yihui’s Blog

什么情况下需要解耦?

日期:2026-09-27
标签:#面试 #场景设计 #消息队列 #Kafka
难度:简单
来源:牛面场景题
答案说明:独立整理(站内题目标记为 VIP,未读取会员答案)

一句话答案

主流程无需等待某个下游结果、且下游变化或故障不应影响主流程时,可以用事件与消息队列做异步解耦。

面试口语版(约 60 秒)

我先看业务承诺:用户提交订单时,库存与支付是否成功决定响应结果,通常不能仅靠发消息就说交易完成;营销通知、统计等后续动作则可异步。对于后者,主流程提交业务状态并可靠地产生事件,消费者按契约独立处理。这样新增营销系统不必修改订单调用链,下游暂时故障也可以积压后重试。但解耦不是“发完就不管”:数据库提交与消息发布之间会有双写空窗,可用事务性 outbox;消费者要幂等,配监控、重试和人工补偿。最后用“已完成”还是“已受理”对外返回,要和实际状态一致。

原理与适用场景

flowchart LR
  U[用户请求] --> S[主业务服务]
  S -->|本地事务| DB[(业务表 + outbox)]
  DB -->|投递器| MQ[事件主题]
  MQ --> C1[营销通知]
  MQ --> C2[统计分析]
  MQ --> C3[审计归档]
  • 调用解耦:生产者只承诺稳定的事件内容;消费者独立部署、处理、重试。
  • 故障隔离:非关键下游不可用时,主流程可继续,但队列容量和最长延迟必须有边界。
  • 双写闭环:将业务状态与待发布事件写在同一个本地事务中,投递器异步发布;投递器至少一次发送,消费者用事件 ID 去重。
  • 何时不适合:请求必须立即得到下游决定,例如信用卡授权结果;此时应保留同步确认或建显式异步状态机。

具体案例与失败分支

新用户注册后发欢迎邮件:注册成功以用户数据提交为准,邮件异步发送。若事务已提交而进程在发消息前崩溃,直接“先写库再发 MQ”会漏邮件;outbox 中的待发布记录能被恢复任务继续投递。若投递成功但标记 outbox 已发送前崩溃,事件可能重发,邮件侧需用唯一发送任务 ID 防重。

取舍与易错点

  • 解耦的是直接调用依赖,不是业务依赖;仍需约定超时、补偿和数据一致性窗口。
  • 不要把必须同步完成的步骤放到异步队列后,却向用户展示“已完成”。
  • 消息模式增加 Broker、契约版本、链路追踪和最终一致性治理成本;简单、低负载且需即时结果的调用不必强行上 MQ。

面试官递进追问

  1. 什么步骤可异步? 看它是否影响当前响应的业务承诺。
  2. 数据库已提交、消息没发出去怎么办? 同库事务性 outbox,加投递重试与对账。
  3. outbox 投递两次怎么办? 消费者按稳定事件 ID 幂等,外部调用再传幂等键。

自测

  • 为“注册成功后发邮件”写出成功响应条件与允许的邮件延迟。
  • 描述双写空窗的两个方向:漏发和重发。
  • 用 60 秒解释“解耦后仍要做一致性与监控”。

参考资料

核对日期:2026-09-27。

Maintained by · YihuiEdit on GitHub

Keep reading

View all posts

如何为Redis分布式锁设置合理的超时时间?

日期:2026-09-27 标签:#面试 #场景设计 #Redis 难度:中等 来源:牛面场景题 答案说明:独立整理(站内题目标记为 VIP,未读取会员答案) 一句话答案 租约应覆盖可预期的执行、暂停与网络抖动,同时限制故障后的等待;没有可靠耗时上界时用有身份校验的受控续期,并在业务资源侧防止旧执行者写入。 面试…

Read article

怎么用Redis实现可重入的分布式锁?

日期:2026-09-27 标签:#面试 #场景设计 #Redis 难度:中等 来源:牛面场景题 答案说明:独立整理(站内题目标记为 VIP,未读取会员答案) 一句话答案 为同一把锁保存“本次最外层获锁的唯一令牌 + 重入次数 + 租约”;嵌套调用共享该令牌并原子递增,释放时递减,次数归零才删除。新一轮独立获锁必…

Read article

基于 Redis 实现分布式锁有什么优缺点?

日期:2026-09-27 标签:#面试 #场景设计 #Redis 难度:简单 来源:牛面场景题 答案说明:独立整理(站内题目标记为 VIP,未读取会员答案) 一句话答案 Redis 锁接入简单、响应快,适合容忍少量故障窗口内重复执行的任务;租约过期与主从切换可能破坏互斥,关键写入还须在资源侧拒绝旧持有者。 面试…

Read article