--- tags: - MQ create time: 2026-05-24 19:52 --- # 事件驱动架构(EDA) ## 概述 事件驱动架构(Event-Driven Architecture, EDA)是系统间通过事件进行通信的架构风格。服务不直接调用对方,而是发布事件、订阅事件,实现松耦合和可扩展性。本文涵盖事件风暴、编排 vs 协同、Saga 模式,以及 EDA 的优势与挑战。 ## 正文 ### EDA 定义 在传统架构中,服务 A 需要调用服务 B,直接发起 RPC 就行。但这也意味着 A 必须知道 B 的存在、地址和接口——**紧耦合**。 EDA 换了一种思路:服务 A 只发布一个事件 "订单已创建",至于谁关心这个事件、怎么处理,A 完全不关心。服务 B、C、D 各自订阅感兴趣的事件,独立响应。 ```go // 事件:描述"发生了什么" type OrderCreatedEvent struct { OrderID string UserID string Amount float64 CreatedAt time.Time } // 发布事件的服务不需要知道谁会处理它 func (s *OrderService) CreateOrder(req CreateOrderReq) error { order := s.repo.Save(req) // 发布事件,不关心谁订阅 s.eventBus.Publish(OrderCreatedEvent{ OrderID: order.ID, UserID: order.UserID, Amount: order.Amount, CreatedAt: time.Now(), }) return nil } ``` ### 事件风暴(Event Storming) 事件风暴是一种协作式的需求分析方法论,由 Alberto Brandolini 提出。核心操作很简单: 1. 找一面大墙,用橙色便利贴写出所有**领域事件**(过去式的动词,如"订单已创建"、"库存已扣减") 2. 按时间线排列事件 3. 识别触发事件的**命令**(蓝色便利贴)和负责处理的**聚合**(黄色便利贴) 4. 发现**策略**(当某事件发生时自动触发的命令)和**读模型** 事件风暴的价值不在于产出完美的设计文档,而在于**让业务和技术人员用同一种语言对话**。一面墙上的便利贴比任何 UML 图都直观。 ### 编排 vs 协同 EDA 中服务间的协作方式有两种模式: #### 编排(Orchestration) 中心协调者指挥各服务做事,像乐队指挥。 ```mermaid graph LR OC["编排器"] -->|"1 创建订单"| OS["订单服务"] OC -->|"2 扣减库存"| IS["库存服务"] OC -->|"3 发起支付"| PS["支付服务"] OC -->|"4 安排发货"| SS["发货服务"] style OC fill:#F44336,color:#fff style OS fill:#2196F3,color:#fff style IS fill:#2196F3,color:#fff style PS fill:#2196F3,color:#fff style SS fill:#2196F3,color:#fff ``` 优点:流程清晰可见,容易调试和监控。缺点:编排器是单点,可能成为瓶颈,服务间仍然有一定耦合。 #### 协同(Choreography) 各服务自主响应事件,像舞蹈演员各自跳舞但配合默契。 ```mermaid graph LR OS["订单服务"] -->|"OrderCreated"| IS["库存服务"] IS -->|"StockReserved"| PS["支付服务"] PS -->|"PaymentDone"| SS["发货服务"] style OS fill:#4CAF50,color:#fff style IS fill:#2196F3,color:#fff style PS fill:#FF9800,color:#fff style SS fill:#9C27B0,color:#fff ``` 优点:真正松耦合,没有单点瓶颈,各服务可独立演进。缺点:流程分散在各服务中,难以全局理解,调试困难。 > [!question] > 编排式 Saga 和协同式 Saga 各有什么优缺点?你在项目中会如何选择? ### Saga 模式 分布式事务的经典问题是:如何跨多个服务保证数据一致性?两阶段提交(2PC)性能差、可用性低。Saga 模式提供了一种更实用的方案:**把长事务拆成多个本地事务,每个本地事务有对应的补偿操作**。 举例:创建订单涉及三个步骤—— 1. 订单服务:创建订单(补偿:取消订单) 2. 库存服务:扣减库存(补偿:恢复库存) 3. 支付服务:发起扣款(补偿:退款) 如果步骤 2 失败,执行步骤 1 的补偿操作(取消订单)。如果步骤 3 失败,执行步骤 2 和 1 的补偿操作。 **编排式 Saga**:由一个 Saga 协调者集中管理流程和补偿逻辑。协调者知道每一步该做什么、失败了怎么回滚。 **协同式 Saga**:没有中心协调者,每个服务监听事件,完成后发布新事件。失败时发布补偿事件,上游服务监听到后自行回滚。 ### 事件驱动的优势 **松耦合**:生产者不依赖消费者,服务可独立开发、部署、扩展。新增一个消费者不需要修改生产者。 **可扩展**:消费者可以水平扩展,消息队列天然支持分区和并行消费。 **天然支持审计**:事件就是操作日志。任何时候都可以回放事件流,重建系统状态。这对金融、医疗等合规要求高的场景非常有价值。 ### 事件驱动的挑战 **最终一致性**:事件驱动天然是异步的,不同服务看到的状态可能有短暂的不一致。需要在业务上接受"最终一致性",而不是强一致。 **调试困难**:请求链路被事件拆散,排查问题时需要在多个服务间追踪 CorrelationID。分布式链路追踪(Jaeger、Zipkin)是必备工具。 **事件风暴(过多事件)**:如果事件粒度太细、订阅关系太复杂,系统会变成"事件风暴"——满天飞的事件让人难以理解业务流程。需要在事件设计上有纪律。 ### Go 代码:协同式 Saga 实现 ```go // 事件定义 type Event struct { Type string // 事件类型 Payload interface{} // 事件数据 TraceID string // 链路追踪ID } // 事件总线 type EventBus struct { subscribers map[string][]func(Event) } func (eb *EventBus) Subscribe(eventType string, handler func(Event)) { eb.subscribers[eventType] = append(eb.subscribers[eventType], handler) } func (eb *EventBus) Publish(event Event) { for _, handler := range eb.subscribers[event.Type] { go handler(event) // 异步处理 } } // 库存服务:监听订单创建事件,扣减库存 func InventoryService(bus *EventBus) { bus.Subscribe("OrderCreated", func(e Event) { order := e.Payload.(OrderCreatedEvent) err := deductStock(order.Items) if err != nil { // 扣减失败,发布补偿事件 bus.Publish(Event{ Type: "StockDeductFailed", Payload: order, TraceID: e.TraceID, }) return } // 扣减成功,发布下一步事件 bus.Publish(Event{ Type: "StockReserved", Payload: order, TraceID: e.TraceID, }) }) } // 支付服务:监听库存预留事件 func PaymentService(bus *EventBus) { bus.Subscribe("StockReserved", func(e Event) { order := e.Payload.(OrderCreatedEvent) err := chargePayment(order.UserID, order.Amount) if err != nil { // 支付失败,触发库存恢复 bus.Publish(Event{ Type: "PaymentFailed", Payload: order, TraceID: e.TraceID, }) return } bus.Publish(Event{ Type: "PaymentDone", Payload: order, TraceID: e.TraceID, }) }) } // 订单服务:监听支付失败事件,取消订单(补偿) func OrderCompensation(bus *EventBus) { bus.Subscribe("PaymentFailed", func(e Event) { order := e.Payload.(OrderCreatedEvent) cancelOrder(order.OrderID) fmt.Printf("Order %s compensated\n", order.OrderID) }) } ``` 这个简化实现展示了协同式 Saga 的核心:每个服务只关心自己监听的事件和需要发布的事件。失败时通过补偿事件触发回滚链。`TraceID` 贯穿整个链路,方便追踪和调试。 ## 关联笔记 - [[31-MQ-背压与流控]] - [[32-MQ-请求-回复模式]] - [[33-MQ-与流处理]]