Software Architect · 模块 12
同步给你一个简单的心智模型。异步给你韧性与解耦——但它要求新的 guarantee。
Queue · event · outbox · backpressure · retry
选同步还是异步,是在"立刻给出答案"和"在时间维度上可靠地处理"之间做选择。
同步调用适合一个问题
如果收银员问一件商品的价格,答案现在就要。如果商店给会计送一份报表,那可以走队列。
当用户现在就需要结果时,同步请求很好:校验密码、展示价格、确认权限。它对 UX 和调试都更简单,但它把调用方和被调用方的可用性绑在了一起。
异步适合那些可以稍后完成的活儿:邮件、索引、analytics、webhook 投递、报表生成、enrichment。它降低耦合,但要求 status、retry、dead-letter queue 和 observability。
event 是一个事实,不是一个命令
"门开着"是一个事实。"把门打开"是一个命令。你可以把两者混在一起,但系统会开始为"意图"争论不休。
event 描述某件已经发生的事:OrderPlaced、PaymentCaptured、UserRegistered。command 则请求某件事发生:CapturePayment、SendEmail。在架构里,不把这两种形式混用是划算的。
如果一个 event 被当成隐藏的 command 来用,consumer 就会依赖 producer 的内部意图。这会破坏 loose coupling(松耦合)。
异步不该成为"错误悄悄消失"的地方。
例子:transactional outbox
书记员先把信登记进台账,然后才寄出。即便收发室宕了,这条记录也不会丢。
创建订单时,service 在同一个事务里写入订单和 outbox event。一个独立的 worker 读取 outbox,把 event 发布到 broker。如果 broker 暂时不可用,event 留在数据库里,稍后会被发出去。
outbox 堵上了一个经典的漏洞:数据已经存了,但 event 在 commit 和 publish 之间丢了。
反例:把队列当成垃圾桶
收件箱不是一个流程。如果没人去处理它,活儿只是被藏起来了。
团队把每一个重操作都丢进队列,却不定义 retry policy、幂等性、ordering、visibility timeout、DLQ 或告警。到了生产环境,消息要么卡住、要么重复、要么处理上好几个小时。
队列本身并不会让系统变可靠。它只是把复杂度从请求路径,挪进了后台处理。
- 用户现在就需要结果吗? - 如果一条消息被投递了两次,会发生什么? - "这个 event 必须被发布"这个事实, 存在哪里? - 系统如何展示积压和失败的 job?