事件驱动架构
解释系统如何通过已经发生的事实事件解耦生产者与消费者,并区分 Event、Command、Queue、Pub/Sub、Event Log 和 UI/Runtime 流式事件的不同语义。
[!info] related notes
- 所属 MOC:Backend Architecture MOC、Messaging & Queues MOC
- 通用语义:消息投递语义、幂等消费者
- 实现:Apache Kafka、RabbitMQ
- Agent:AI Agent Application MOC
事件驱动架构
[!abstract] 一句话定义 事件驱动架构让组件通过“已经发生的事实”异步协作。Producer 发布 Event,Broker / Event Bus 传递或保存,Consumer 独立响应;它降低时间和部署耦合,但引入最终一致性、重复、顺序、Schema 和跨系统排障成本。
Command、Event 与 Event Log
Command
→ “请执行某动作”
→ 可能被拒绝
Event
→ “某事实已经发生”
→ 使用过去式,原则上不可撤回
Event Log
→ 按顺序保留事件历史
→ 支持 Replay 和独立消费进度
例如:
CreateOrder → Command
OrderCreated → Event
order-events topic→ Event Log
核心组件
Producer
→ Event Contract
→ Broker / Channel / Log
→ Consumer
→ Local Business Effect
事件契约至少应明确:Event ID、Type、Version、Occurred At、Producer、Aggregate / Entity ID、Payload 和必要的 Trace / Tenant 信息。
为什么能降低耦合
同步调用要求双方同时在线,并让调用方知道下游地址和响应。事件模式让 Producer 只发布事实:
Order Service emits OrderCreated
├─ Inventory reserves stock
├─ Notification sends message
└─ Analytics updates metrics
新增 Consumer 不必修改 Producer 主流程。
但它只是降低耦合,不是消除依赖:大家仍依赖 Event Schema、语义和可用性。
事件驱动的五类代价
1. Eventual Consistency
不同 Consumer 在不同时间完成,本地状态会暂时不一致。产品要明确“处理中”状态和可接受延迟。
2. Duplicate Delivery
ACK、Offset 或网络故障会导致重复投递。Consumer 必须幂等,见 幂等消费者。
3. Ordering
Broker 顺序边界、并发消费和 Retry 路径都可能改变业务效果顺序。常用 Entity Key + Version / State Machine 防止旧事件覆盖新状态。
4. Schema Evolution
Producer 和 Consumer 不能总同步发布。优先向后兼容新增字段、明确版本和弃用窗口,避免随意改变字段语义。
5. Observability
一个请求可能变成跨分钟、跨多个 Consumer 的事件链。必须传播 Correlation / Trace ID,并观察 Lag、Retry、DLQ 和最终业务结果。
Event Notification 与 Event-Carried State
Event Notification
OrderCreated { order_id }
Consumer 再调用 Producer 查询详情。Payload 小,但增加同步依赖和查询压力。
Event-Carried State Transfer
OrderCreated { order_id, user_id, amount, items, ... }
Consumer 可独立处理,但事件更大、敏感数据更多,Schema 演进和重复事实治理更复杂。
选择取决于自治、数据敏感度、更新频率和一致性要求。
DB 与 Event 的可靠双写
错误做法:
write DB
→ publish event
两步之间崩溃会出现 DB 已提交但 Event 丢失。
常见解决:
Business Transaction
→ write business row + outbox row atomically
→ publisher / CDC reads outbox
→ publish event
→ mark sent / retry
这仍需幂等 Producer 和 Consumer。
在 Agent Runtime 中的应用
Agent Runtime 可把执行过程表达为事件:
run_started
tool_call_requested
tool_call_completed
approval_required
run_resumed
run_completed
Event 适合前端同步、Trace、Replay 和审计;Runtime 仍需要权威 State / Checkpoint,不能只靠客户端收到的事件恢复。
EDA 与 SSE / WebSocket
- EDA 是系统组件的协作架构;
- SSE / WebSocket 是向客户端传输事件的协议。
后端可以内部用同步调用,外部用 SSE;也可以内部用 Kafka,客户端仍通过普通 HTTP。两者不是同义词。
什么时候值得用
适合:多个独立下游、长任务、削峰、跨服务集成、审计/Replay、状态投影。
不适合:简单强一致 CRUD、极短同步链路、没有 Schema/幂等/观测能力的小系统。
设计检查表
- Event 是已发生事实,不是模糊命令。
- Event ID、Version、Entity Key、Occurred At 明确。
- Producer / Consumer 都可重试且幂等。
- 顺序边界和过期事件处理明确。
- DB + Broker 双写使用 Outbox / CDC 或等价方案。
- 有 Lag、Retry、DLQ、Trace 和业务完成指标。
- 能重建派生视图,且知道 Source of Truth 在哪里。
学习检查
不看正文回答:
- Event 与 Command 的核心语义差异是什么?
- EDA 降低了哪些耦合,又引入了哪些一致性和可观测性成本?
- at-least-once delivery 为什么要求 consumer 幂等?
- ordering 的边界为什么通常需要落到 partition/entity key,而不是追求全局总顺序?
- Event Notification 与 Event-Carried State Transfer 分别牺牲/减少了什么耦合?
- 为什么 DB transaction + broker publish 会产生 dual-write 问题?Outbox/CDC 怎样收敛它?
- SSE / WebSocket 为什么是 transport,而不是 EDA 本身?
- 给一个“订单创建后触发库存、邮件、分析”场景,画出 producer、event contract、broker/log、consumer 与 failure/retry 边界。