事件驱动架构全景解析:从 Event Sourcing 到 Saga 模式的实战指南

为什么你需要了解事件驱动架构

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
// Command 端:处理下单命令
@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());
}
}

// Projection:消费事件更新读模型
@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()
);
}
}
}

// Query 端:直接查读模型,毫秒级响应
@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
// Saga 协调器
@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 {
// Step 1: 创建订单
orderService.createOrder(orderId, request);

// Step 2: 扣减库存
inventoryService.reserve(orderId, request.getItems());

// Step 3: 发起支付
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
// 状态机驱动的 Saga
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 并非各自为战,而是相互依存:

  1. Saga 产生事件——每一步操作完成后发出事件,驱动下一步
  2. Event Store 记录事件——Saga 的每一步、每一个补偿操作都有完整的事件记录
  3. CQRS 构建视图——消费这些事件,为前端提供高效的查询接口
1
2
3
4
5
6
7
8
9
用户下单 → Saga 协调器 → 订单服务(产生事件)
→ 库存服务(产生事件)
→ 支付服务(产生事件)

Event Store (全量记录)

Projection (构建读模型)

前端查询 (CQRS 读端)

这三者的组合,本质上是用事件作为系统各部分之间的通信契约。事件天然的不可变性和可追溯性,使得系统既灵活又可靠。

落地建议

  1. 不要为了 Event Sourcing 而 Event Sourcing——如果系统没有审计需求、不需要时间追溯、业务逻辑简单,CRUD 够用就好。引入不必要的复杂度是最常见的架构反模式。

  2. 先从 Saga 模式入手——微服务之间的数据一致性问题是最紧迫的,Saga 模式的投入产出比最高。

  3. 事件设计是核心——事件的命名、粒度、字段设计直接决定系统的可维护性。遵循”事件是已经发生的事实”这一原则,用过去式命名(OrderCreated 而非 CreateOrder)。

  4. 监控不可少——事件驱动系统天然分布、异步,出了问题排查困难。建议引入分布式链路追踪(OpenTelemetry)和事件流监控(Kafka Lag Monitor)。

  5. 演进式设计——初期可以只用 Saga + 简单事件表,等业务复杂度上来后再引入 Event Sourcing 和 CQRS,不要一步到位。

事件驱动架构不是什么新概念,但在 2026 年的微服务生态下,随着 Kafka、Pulsar 等流处理平台的成熟,以及云原生基础设施对事件驱动的原生支持(CloudEvents 标准),这套模式正在从”大厂专属”走向标配。理解它的核心思想和模式网络,是每一个后端开发者进阶架构师的必修课。