为什么你需要了解事件驱动架构
2026 年的后端开发,早已不是”写个 CRUD 接口”那么简单。当你的系统拆分成十几个微服务,订单、库存、支付、物流各自为战时,一个看似简单的”用户下单”操作,实际上横跨了四个服务的多次状态变更。
传统 REST 同步调用的问题暴露无遗:链路一长就超时,一个服务挂了全链路瘫痪,出了问题日志散落在各个服务里无法还原现场。
事件驱动架构(Event-Driven Architecture, EDA) 正是为解决这些问题而生的一套完整方法论。它不是一个单一技术,而是一个模式网络——Event Sourcing 提供状态追溯能力,CQRS 实现读写分离优化,Saga 模式保障跨服务事务最终一致性。三者环环相扣,构成了现代分布式系统设计的核心骨架。
Event Sourcing:不存状态,存历史
核心思想
传统数据库设计存储的是当前状态:UPDATE account SET balance = 800 WHERE id = 1。你只知道余额是 800,但钱是怎么变少的?不知道。
Event Sourcing 反其道而行——只记录事件,不直接修改状态。每一笔变动都是一个不可变的事件对象:
1 2 3 4
| Event 1: AccountCreated { id: 1, initialBalance: 1000 } Event 2: MoneyDeposited { id: 1, amount: 500 } Event 3: MoneyWithdrawn { id: 1, amount: 200 } Event 4: MoneyDeposited { id: 1, amount: 500 }
|
当前状态?从头回放一遍事件即可:1000 + 500 - 200 + 500 = 1800。
Java 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public abstract class DomainEvent { private final String aggregateId; private final Instant timestamp; private final long version;
protected DomainEvent(String aggregateId, long version) { this.aggregateId = aggregateId; this.timestamp = Instant.now(); this.version = version; } }
public class OrderCreated extends DomainEvent { private final String userId; private final List<OrderItem> items;
public OrderCreated(String orderId, long version, String userId, List<OrderItem> items) { super(orderId, version); this.userId = userId; this.items = items; } }
public class OrderPaid extends DomainEvent { private final BigDecimal amount; private final String paymentId;
public OrderPaid(String orderId, long version, BigDecimal amount, String paymentId) { super(orderId, version); this.amount = amount; this.paymentId = paymentId; } }
public class OrderAggregate { private String orderId; private OrderStatus status; private List<OrderItem> items; private BigDecimal totalAmount;
public static OrderAggregate fromEvents(List<DomainEvent> events) { OrderAggregate order = new OrderAggregate(); for (DomainEvent event : events) { order.apply(event); } return order; }
private void apply(DomainEvent event) { if (event instanceof OrderCreated e) { this.orderId = e.getAggregateId(); this.items = e.getItems(); this.status = OrderStatus.CREATED; this.totalAmount = calculateTotal(e.getItems()); } else if (event instanceof OrderPaid e) { this.status = OrderStatus.PAID; } } }
|
事件存储(Event Store)
事件需要持久化。可以用关系型数据库,也可以用专门的事件存储:
1 2 3 4 5 6 7 8 9 10
| CREATE TABLE event_store ( global_id BIGSERIAL PRIMARY KEY, aggregate_id VARCHAR(64) NOT NULL, event_type VARCHAR(128) NOT NULL, event_data JSONB NOT NULL, version BIGINT NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE (aggregate_id, version) );
|
适用场景与取舍
Event Sourcing 并非银弹。它天然适合审计要求高(金融、医疗)、需要时间旅行(调试历史状态)、事件驱动天然契合的场景。代价是事件 schema 演进困难、查询需要物化视图、初学者心智负担重。
CQRS:读写分离的极致
Event Sourcing 解决了”状态怎么来”的问题,但引出了新问题:从事件流回放来读取数据,查询效率极低。这时候就需要 CQRS(Command Query Responsibility Segregation)上场了。
读写模型分离
CQRS 的核心是把系统分成两个独立的子系统:
- 写端(Command Side):处理业务命令,产生事件,写入 Event Store
- 读端(Query Side):消费事件,构建针对查询优化的物化视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ┌──────────────┐ commands ┌──────────────┐ │ API 层 │ ──────────────▶│ Command Handler│ └──────────────┘ └──────┬───────┘ │ events ▼ ┌──────────────┐ │ Event Store │ └──────┬───────┘ │ projection ▼ ┌──────────────┐ queries ┌──────────────┐ │ Read Model │ ◀───────────│ Query Side │ │ (MySQL/ES) │ └──────────────┘ └──────────────┘
|
用 Spring Boot 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| @Service public class OrderCommandHandler { @Autowired private EventStore eventStore;
@Transactional public void handle(CreateOrderCommand cmd) { OrderAggregate order = OrderAggregate.create(cmd); List<DomainEvent> events = order.getUncommittedEvents();
eventStore.saveEvents(cmd.getOrderId(), events, cmd.getExpectedVersion()); } }
@Component public class OrderProjection { @Autowired private JdbcTemplate jdbc;
@KafkaListener(topics = "order-events") public void onEvent(ConsumerRecord<String, DomainEvent> record) { DomainEvent event = record.value();
if (event instanceof OrderCreated e) { jdbc.update( "INSERT INTO order_view (order_id, user_id, status, total, created_at) " + "VALUES (?, ?, 'CREATED', ?, ?)", e.getAggregateId(), e.getUserId(), e.getTotalAmount(), e.getTimestamp() ); } else if (event instanceof OrderPaid e) { jdbc.update( "UPDATE order_view SET status = 'PAID' WHERE order_id = ?", e.getAggregateId() ); } } }
@RestController public class OrderQueryController { @GetMapping("/orders/{id}") public OrderView getOrder(@PathVariable String id) { return orderReadRepository.findById(id) .orElseThrow(() -> new OrderNotFoundException(id)); } }
|
CQRS 的优势在于读写可以独立扩展——写端保证业务正确性,读端可以针对查询场景用不同的存储(Elasticsearch 做全文搜索、Redis 做热点缓存),互不干扰。
Saga 模式:跨服务事务的最终一致性
微服务架构下,一个”下单”操作涉及订单服务、库存服务、支付服务。分布式事务(如 2PC)在高并发场景下性能堪忧且可用性差。Saga 模式通过将大事务拆成一系列本地事务 + 补偿操作来实现最终一致性。
两种编排方式
编排式(Choreography):每个服务监听事件并自行决策,去中心化但链路复杂时难以追踪。
协调式(Orchestration):由一个 Saga 协调器统一指挥各步操作。推荐在复杂场景下使用协调式,因为流程清晰、易于维护。
协调式 Saga 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Service public class CreateOrderSaga { @Autowired private OrderService orderService; @Autowired private InventoryService inventoryService; @Autowired private PaymentService paymentService;
public void execute(CreateOrderRequest request) { String orderId = UUID.randomUUID().toString();
try { orderService.createOrder(orderId, request);
inventoryService.reserve(orderId, request.getItems());
paymentService.charge(orderId, request.getAmount());
} catch (InventoryException e) { orderService.cancelOrder(orderId); throw new OrderFailedException("库存不足", e);
} catch (PaymentException e) { inventoryService.release(orderId); orderService.cancelOrder(orderId); throw new OrderFailedException("支付失败", e); } } }
|
对于长时间运行的业务(如电商下单后 30 分钟未支付自动取消),可以结合状态机和定时任务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class OrderSagaState { private OrderStatus current;
public void transition(OrderStatusEvent event) { this.current = switch (this.current) { case CREATED -> { if (event == OrderStatusEvent.STOCK_RESERVED) yield OrderStatus.STOCK_RESERVED; throw new IllegalStateException("无效的状态转换"); } case STOCK_RESERVED -> { if (event == OrderStatusEvent.PAYMENT_SUCCESS) yield OrderStatus.COMPLETED; yield OrderStatus.COMPENSATING; } case COMPENSATING -> { compensate(); yield OrderStatus.CANCELLED; } default -> this.current; }; } }
|
三者如何协同
在实际项目中,Event Sourcing、CQRS 和 Saga 并非各自为战,而是相互依存:
- Saga 产生事件——每一步操作完成后发出事件,驱动下一步
- Event Store 记录事件——Saga 的每一步、每一个补偿操作都有完整的事件记录
- CQRS 构建视图——消费这些事件,为前端提供高效的查询接口
1 2 3 4 5 6 7 8 9
| 用户下单 → Saga 协调器 → 订单服务(产生事件) → 库存服务(产生事件) → 支付服务(产生事件) ↓ Event Store (全量记录) ↓ Projection (构建读模型) ↓ 前端查询 (CQRS 读端)
|
这三者的组合,本质上是用事件作为系统各部分之间的通信契约。事件天然的不可变性和可追溯性,使得系统既灵活又可靠。
落地建议
不要为了 Event Sourcing 而 Event Sourcing——如果系统没有审计需求、不需要时间追溯、业务逻辑简单,CRUD 够用就好。引入不必要的复杂度是最常见的架构反模式。
先从 Saga 模式入手——微服务之间的数据一致性问题是最紧迫的,Saga 模式的投入产出比最高。
事件设计是核心——事件的命名、粒度、字段设计直接决定系统的可维护性。遵循”事件是已经发生的事实”这一原则,用过去式命名(OrderCreated 而非 CreateOrder)。
监控不可少——事件驱动系统天然分布、异步,出了问题排查困难。建议引入分布式链路追踪(OpenTelemetry)和事件流监控(Kafka Lag Monitor)。
演进式设计——初期可以只用 Saga + 简单事件表,等业务复杂度上来后再引入 Event Sourcing 和 CQRS,不要一步到位。
事件驱动架构不是什么新概念,但在 2026 年的微服务生态下,随着 Kafka、Pulsar 等流处理平台的成熟,以及云原生基础设施对事件驱动的原生支持(CloudEvents 标准),这套模式正在从”大厂专属”走向标配。理解它的核心思想和模式网络,是每一个后端开发者进阶架构师的必修课。