事件驱动架构有哪些优缺点?
简化版
事件驱动架构让生产者发布“已经发生的事实”,多个消费者独立响应,优点是解耦、削峰和易扩展;代价是最终一致性、重复/乱序、Schema 演进、链路排障和运维复杂度。不是把同步 RPC 换成 MQ 就自动得到事件驱动。
详细版
事件应以业务事实命名并由拥有数据的服务发布,包含稳定 eventId、聚合 ID、版本和发生时间。生产者用 Outbox 保证数据与事件一致,消费者幂等、按明确 key 维护局部顺序,失败进入重试和死信。
查询结果或必须立即返回的强一致校验仍适合同步调用;可延后执行、允许最终一致且有多个订阅者的流程更适合事件。落地时必须建设 Schema 治理、追踪、重放、积压容量和对账,而不是隐藏跨服务依赖。
完整版教学
一、事件与命令的区别
命令表达“请做某事”,通常有目标接收者;事件表达“某事实已发生”,可被零到多个订阅者观察。CreateInvoice 是命令,OrderPaid 是事件。把命令伪装成事件会让所有权和失败责任变模糊。
心法:事件用过去式描述不可随意撤销的事实,消费者决定如何响应。
二、解耦带来的收益
订单服务无需知道积分、通知、风控的地址,只发布付款事件;新消费者可独立接入。Broker 缓冲短时峰值,生产者与消费者可按不同速率扩容。故障消费者恢复后从位点继续,不阻塞主交易响应。
Order -> OrderPaid -> [Points]
-> [Notification]
-> [Risk]
三、解耦不等于没有依赖
依赖从同步 API 转移到 Topic、Schema、语义和时序。生产者删除字段或改变含义会同时影响多个未知消费者;积压让错误延迟暴露。必须有事件目录、owner、兼容规则和影响分析。
事件目录还应标明事实来源和唯一发布者,防止多个服务分别发布语义相近的 OrderPaid,让消费者无法判断哪个才是权威事实。
| 维度 | 同步 RPC | 事件驱动 |
|---|---|---|
| 响应 | 即时结果 | 异步收敛 |
| 耦合 | 地址/API | Schema/语义 |
| 故障 | 直接返回 | 积压/重试 |
| 排障 | 调用栈 | Trace+消息轨迹 |
四、一致性如何建立
业务写成功但事件未发会造成下游永远不知道;先发事件再提交又可能发布不存在的事实。Outbox 把业务数据和事件记录放在同一本地事务,后台至少一次发送;消费者通过 eventId 与状态机幂等。
最终一致性必须有最大收敛时间、补偿和对账,不能只说“过一会会一致”。
五、顺序、重复与重放
Broker 通常只保证分区内顺序,应以 orderId 等聚合 ID 分区。重试和故障切换会重复,跨分区事件会乱序;消费者保存版本、忽略旧事件或等待缺口。重放旧事件时还要保证当前代码兼容历史 Schema。
例如同一订单 v3 先于 v2 到达,消费者不能让 v2 覆盖 v3,可按版本 CAS 更新。
六、何时不该使用
用户登录鉴权、余额是否足够等必须立即得到权威结果的校验,不应为了“解耦”改成异步猜测。团队缺少 Broker 运维、幂等、监控和对账能力时,引入事件会增加事故面。
评审应比较业务延迟容忍、订阅者数量、峰值削峰需求、失败恢复和调试成本。
对只有一个稳定调用者、要求立即反馈且没有削峰需求的流程,同步接口通常更直观;架构选择应服务于约束,而不是追求事件数量。
七、常见误区与追问
- 误区:用了 MQ 就是事件驱动。 消息可能只是远程命令或异步 RPC。
- 误区:异步等于完全解耦。 Schema 与业务语义仍是契约。
- 误区:最终一致性不需要 SLA。 必须定义最大收敛时间和补偿。
- 误区:事件消费者可以假设只收到一次。 至少一次链路天然可能重复。
- 追问:数据与事件如何一致? 本地事务 Outbox 或可靠 CDC。
- 追问:如何排查一次业务? 通过 eventId、traceId、位点和消费结果串联。
- 追问:事件能修改吗? 已发布事实不可原地改,应发纠正/补偿事件并演进 Schema。
八、加强记忆
事件驱动沿“事实建模—可靠发布—幂等消费—顺序边界—失败恢复—可观测”落地。它用异步与最终一致换解耦、削峰和扩展;依赖并未消失,只是从调用地址转为事件契约,因此 Schema、重放和对账是核心能力。