跳至主要內容

事件驱动架构:从理念到落地

郑天祺大约 14 分钟架构设计事件驱动EDAKafkaRocketMQ架构模式

前言

如果你写过 Spring Boot 项目,你大概率用过 @EventListener 或消息队列。但"用消息队列"和"事件驱动架构"是两回事。事件驱动架构(Event-Driven Architecture, EDA)是一种架构风格,不只是技术选型。

本文带你从"为什么需要事件驱动"开始,理解三种事件模式、Command vs Event 的本质区别,再到实战落地。


第一部分:什么是事件驱动架构

1.1 一个简单的对比

传统请求-响应模式(你每天都在写的):

// Controller → Service → 同步返回
@PostMapping("/orders")
public Result<OrderResponse> createOrder(@RequestBody OrderRequest request) {
    // 1. 校验库存
    // 2. 计算价格
    // 3. 扣减库存
    // 4. 保存订单
    // 5. 发送通知
    // 6. 记录日志
    // 7. 返回结果
    return Result.success(response);
}
// 所有事情串行完成,一步慢了全卡

事件驱动模式

@PostMapping("/orders")
public Result<OrderResponse> createOrder(@RequestBody OrderRequest request) {
    // 1. 保存订单(核心操作)
    Order order = orderService.create(request);

    // 2. 发布事件(其他操作异步化)
    eventPublisher.publish(new OrderCreatedEvent(order));

    // 3. 立即返回
    return Result.success(OrderResponse.from(order));
}

// 事件消费者各自处理:
// - InventoryListener: 扣库存
// - NotificationListener: 发短信
// - LogListener: 记录审计日志
// - AnalyticsListener: 更新实时报表
// 异步并行处理,不阻塞主流程

1.2 EDA 的核心思想

事件驱动架构的核心非常简单:一个系统组件发布事件,其他感兴趣的组件接收并响应事件

┌──────────┐  事件     ┌──────────────┐  事件     ┌──────────┐
│ 订单服务 │ ────────→ │  消息代理     │ ────────→ │ 库存服务 │
│          │           │  (Event Bus)  │           │          │
│ 发布者   │           │              │           │ 消费者   │
└──────────┘           └──────┬───────┘           └──────────┘
                              │
                    ┌─────────┼─────────┐
                    ▼         ▼         ▼
              ┌────────┐┌────────┐┌────────┐
              │通知服务││日志服务││分析服务│
              │消费者  ││消费者  ││消费者  │
              └────────┘└────────┘└────────┘

三个关键词:解耦、异步、最终一致性


第二部分:三种事件模式

2.1 模式一:事件通知(Event Notification)

最简单的模式。事件只携带发生了什么的标识信息,不携带状态数据。消费者收到事件后,如果需要数据,自己回调查询。

事件内容:
{
  "eventType": "OrderCreated",
  "orderId": "ORD-2026-001",
  "timestamp": "2026-06-11T10:30:00Z"
}
(只有订单 ID,没有订单详细信息)
// 发布者
public class OrderService {
    public void createOrder(CreateOrderCommand cmd) {
        Order order = orderRepository.save(cmd.toOrder());
        // 只发通知,不携带数据
        eventBus.publish(new OrderCreatedEvent(order.getId()));
    }
}

// 消费者
@Component
public class InventoryListener {

    @Autowired private OrderClient orderClient;  // 回调查询

    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        // 需要数据?自己回调
        OrderDTO order = orderClient.getOrder(event.getOrderId());
        inventoryService.deduct(order.getItems());
    }
}

优点:事件体小,传输效率高
缺点:消费者需要回调查询,增加耦合和延迟;源服务压力增大(大量回调查询)

2.2 模式二:事件携带状态转移(Event-Carried State Transfer)

事件携带足够的数据,消费者不需要回调源服务。这是最推荐的生产实践。

事件内容:
{
  "eventType": "OrderCreated",
  "orderId": "ORD-2026-001",
  "customerId": "CUS-1001",
  "items": [
    {"productId": "SKU-001", "quantity": 2, "price": 99.00},
    {"productId": "SKU-002", "quantity": 1, "price": 199.00}
  ],
  "totalAmount": 397.00,
  "shippingAddress": {
    "province": "北京",
    "city": "北京",
    "detail": "朝阳区xxx"
  },
  "timestamp": "2026-06-11T10:30:00Z"
}
(包含消费者需要的所有信息)
// 发布者:携带完整状态
public class OrderService {
    public void createOrder(CreateOrderCommand cmd) {
        Order order = orderRepository.save(cmd.toOrder());

        // 发布携带完整状态的事件
        eventBus.publish(OrderCreatedEvent.from(order));
        // OrderCreatedEvent.from(order) 将订单所有需要的字段都放入事件
    }
}

// 消费者:无需回调查询,完全自包含
@Component
public class InventoryListener {

    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        // 直接从事件中获取所有需要的数据
        for (OrderItem item : event.getItems()) {
            inventoryService.deduct(item.getProductId(), item.getQuantity());
        }
    }
}

最佳实践:携带"消费者需要但未来不会频繁改变"的数据。注意:不要在事件中包含消费者不需要的敏感数据

2.3 模式三:事件溯源(Event Sourcing)

最"重"的模式。不存储当前状态,而是存储所有状态变更的事件序列。当前状态 = 所有事件的"回放"结果。

用户余额的变化:
  事件序列:
    INIT:         余额 = 0
    + DepositEvent(1000)    → 余额 = 1000
    + WithdrawEvent(300)    → 余额 = 700
    + DepositEvent(500)     → 余额 = 1200
    + WithdrawEvent(200)    → 余额 = 1000

当前余额 = SUM(所有事件的金额变化) = 1000
/**
 * 事件溯源版本的账户服务
 */
public class Account {

    private AccountId id;
    private List<AccountEvent> events = new ArrayList<>();

    // 不是存储"余额 = 1000",而是记录每个事件
    public void deposit(Money amount) {
        apply(new MoneyDepositedEvent(this.id, amount, LocalDateTime.now()));
    }

    public void withdraw(Money amount) {
        if (getCurrentBalance().lessThan(amount)) {
            throw new InsufficientBalanceException();
        }
        apply(new MoneyWithdrawnEvent(this.id, amount, LocalDateTime.now()));
    }

    private void apply(AccountEvent event) {
        events.add(event);
    }

    // 当前余额 = 回放所有事件
    public Money getCurrentBalance() {
        return events.stream()
            .map(e -> switch (e) {
                case MoneyDepositedEvent de -> de.getAmount();
                case MoneyWithdrawnEvent we -> we.getAmount().negate();
                default -> Money.ZERO;
            })
            .reduce(Money.ZERO, Money::add);
    }

    // 从事件序列重建账户
    public static Account fromEvents(List<AccountEvent> events) {
        Account account = new Account();
        events.forEach(e -> account.apply(e));
        return account;
    }
}

事件溯源的优势

  • 完整的审计日志(谁在什么时候做了什么)
  • 可以回溯到任意历史状态
  • 方便分析业务行为模式

事件溯源的代价

  • 实现复杂度高
  • 数据量大(事件数量远大于状态数量)
  • 查询当前状态需要回放(可通过快照优化)

金融、担保、审计等强监管场景特别适合事件溯源。

2.4 三种模式的选择指南

模式适用场景不适用场景
事件通知消费者需要最新的实时数据高并发、希望完全解耦
携带状态转移大多数场景(推荐)事件数据过大(超过 MB 级)
事件溯源金融/审计/需要完整历史简单 CRUD、高 QPS 查询

第三部分:Command vs Event

3.1 本质区别

这是最容易混淆的概念之一。

维度Command(命令)Event(事件)
意图请求某事发生告知某事已发生
方向指向特定接收者广播给关心的人
命名祈使句:CreateOrder过去式:OrderCreated
可否拒绝可以不可拒绝(已经发生了!)
一个 vs 多个一个命令一个接收者一个事件多个消费者
时间未来时过去时
// Command:请求做某事(可能被拒绝)
public class CreateOrderCommand {
    private CustomerId customerId;
    private List<OrderItem> items;
    private Money totalAmount;
}
// "请帮我创建一个订单"

// Event:告知某事已发生(不可否认)
public class OrderCreatedEvent {
    private OrderId orderId;
    private CustomerId customerId;
    private List<OrderItem> items;
    private Money totalAmount;
    private LocalDateTime createdAt;
}
// "订单已创建,编号 ORD-2026-001"

3.2 实践:Command 转 Event

@Service
public class OrderApplicationService {

    // 接收 Command
    public OrderCreatedEvent createOrder(CreateOrderCommand command) {
        // 1. 处理命令(可能失败)
        Order order = Order.create(command);

        // 2. 持久化
        orderRepository.save(order);

        // 3. 返回事件(告知世界发生了什么)
        return new OrderCreatedEvent(
            order.getId(),
            order.getCustomerId(),
            order.getItems(),
            order.getTotalAmount(),
            Instant.now()
        );
        // 如果步骤 1 失败了,步骤 2-3 都不会执行,事件不会产生
    }
}

一句话区分

  • 命令朋友:"帮我买杯咖啡" → Command(可能被拒绝:"我没空")
  • 告诉朋友:"我刚买了杯咖啡" → Event(事实已发生,无法拒绝)

第四部分:消息代理的选择

4.1 三大消息队列对比

特性KafkaRocketMQRabbitMQ
设计目标高吞吐日志流金融级可靠消息灵活路由
吞吐量极高(百万/秒)高(十万/秒)中(万/秒)
延迟毫秒级毫秒级微秒级
持久化磁盘顺序写同步/异步刷盘内存+磁盘
事务消息支持(0.11+)原生支持支持(但复杂)
顺序消息分区内有序支持支持
延迟消息不支持原生支持(18级)插件支持
消息回溯支持(按时间)支持(按时间)不支持
协议自有协议自有协议AMQP
运维复杂度
Java 生态Spring KafkaSpring Cloud StreamSpring AMQP

4.2 Kafka:事件流的最佳选择

// Kafka 生产者和消费者
@Service
public class KafkaOrderEventPublisher {

    @Autowired private KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate;

    public void publish(OrderCreatedEvent event) {
        // 使用 orderId 作为 key,保证同一个订单的事件有序
        kafkaTemplate.send("order-events",
            event.getOrderId().toString(),  // key
            event                            // value
        );
    }
}

@Component
public class KafkaInventoryConsumer {

    @KafkaListener(topics = "order-events", groupId = "inventory-group")
    public void onOrderCreated(
            @Payload OrderCreatedEvent event,
            @Header(KafkaHeaders.RECEIVED_KEY) String key) {

        log.info("收到订单事件: orderId={}, key={}", event.getOrderId(), key);

        for (OrderItem item : event.getItems()) {
            inventoryService.deduct(item.getProductId(), item.getQuantity());
        }
    }
}
# Kafka 生产者配置
spring:
  kafka:
    producer:
      bootstrap-servers: kafka-broker:9092
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
      # 可靠性配置
      acks: all                       # 所有 ISR 确认
      retries: 3
      enable-idempotence: true        # 幂等生产者
      compression-type: snappy        # 压缩

4.3 RocketMQ:金融场景的首选

// RocketMQ 事务消息 - 实现分布式事务
@Service
public class RocketMQOrderService {

    @Autowired private RocketMQTemplate rocketMQTemplate;

    @Transactional
    public void createOrder(CreateOrderCommand command) {
        Order order = orderRepository.save(command.toOrder());

        // 事务消息:先发半消息,本地事务提交后消息才投递
        rocketMQTemplate.sendMessageInTransaction(
            "order-topic",
            MessageBuilder.withPayload(OrderCreatedEvent.from(order)).build(),
            order.getId()  // 传递给事务检查器的参数
        );
    }
}

// 事务检查器:处理半消息的状态回查
@RocketMQTransactionListener
public class OrderTransactionListener
        implements RocketMQLocalTransactionListener {

    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(
            Message msg, Object arg) {
        try {
            Long orderId = (Long) arg;
            // 检查订单是否已成功创建
            Order order = orderRepository.findById(new OrderId(orderId))
                .orElse(null);
            if (order != null) {
                return RocketMQLocalTransactionState.COMMIT;
            }
            return RocketMQLocalTransactionState.ROLLBACK;
        } catch (Exception e) {
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(
            Message msg) {
        // 回查逻辑:消息服务端定期回调此方法确认事务状态
        Long orderId = extractOrderId(msg);
        return orderRepository.existsById(new OrderId(orderId))
            ? RocketMQLocalTransactionState.COMMIT
            : RocketMQLocalTransactionState.ROLLBACK;
    }
}

// 消费者
@Service
@RocketMQMessageListener(
    topic = "order-topic",
    consumerGroup = "inventory-group",
    consumeMode = ConsumeMode.ORDERLY  // 顺序消费
)
public class InventoryConsumer
        implements RocketMQListener<OrderCreatedEvent> {

    @Override
    public void onMessage(OrderCreatedEvent event) {
        // RocketMQ 保证重试,需要实现幂等
        if (inventoryDeductLogRepository.exists(event.getOrderId())) {
            return;  // 幂等:已处理过
        }

        inventoryService.deduct(event.getItems());

        // 记录处理日志(用于幂等判断)
        inventoryDeductLogRepository.save(
            new DeductLog(event.getOrderId()));
    }
}

第五部分:实战——订单系统的事件驱动改造

5.1 改造前(同步耦合)

@Service
public class OrderService {

    @Autowired private InventoryService inventoryService;
    @Autowired private PaymentService paymentService;
    @Autowired private NotificationService notificationService;
    @Autowired private CouponService couponService;
    @Autowired private LogService logService;

    @Transactional
    public Order createOrder(OrderRequest request) {
        // 同步调用所有服务 → 耦合严重
        inventoryService.deduct(request.getItems());     // 2s
        couponService.use(request.getCouponId());         // 0.5s
        Order order = saveOrder(request);                 // 0.1s
        paymentService.createPayment(order);              // 1s
        notificationService.sendSms(order);               // 0.5s
        logService.recordAuditLog(order);                 // 0.2s
        return order;
        // 总耗时: 4.3 秒!用户体验极差
    }
}

5.2 改造后(事件驱动)

@Service
public class OrderService {

    @Autowired private OrderRepository orderRepository;
    @Autowired private DomainEventPublisher eventPublisher;

    @Transactional
    public Order createOrder(CreateOrderCommand command) {
        // 1. 创建订单(核心操作)
        Order order = Order.create(command);
        orderRepository.save(order);

        // 2. 发布领域事件
        eventPublisher.publish(OrderCreatedEvent.from(order));

        // 3. 立即返回
        return order;
        // 总耗时: ~0.1 秒
    }
}

// === 各消费者独立处理 ===

// 库存服务 - 扣减库存
@Component
public class OrderInventoryHandler {

    @EventListener
    @Transactional
    public void handleOrderCreated(OrderCreatedEvent event) {
        for (OrderItem item : event.getItems()) {
            inventoryRepository.deduct(
                item.getProductId(), item.getQuantity());
        }
    }
}

// 支付服务 - 创建支付单
@Component
public class OrderPaymentHandler {

    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        paymentService.createPayment(
            event.getOrderId(), event.getTotalAmount());
    }
}

// 通知服务 - 发送短信
@Component
public class OrderNotificationHandler {

    @EventListener
    @Async  // 异步处理
    public void handleOrderCreated(OrderCreatedEvent event) {
        notificationService.sendOrderConfirmation(
            event.getCustomerId(), event.getOrderId());
    }
}

// 审计日志服务
@Component
public class OrderAuditLogHandler {

    @EventListener
    @Async
    public void handleOrderCreated(OrderCreatedEvent event) {
        auditLogRepository.save(new AuditLog(
            "ORDER_CREATED",
            event.getOrderId(),
            event.getCustomerId(),
            Instant.now()
        ));
    }
}

5.3 事件版本管理

事件结构会随着业务发展而变化,需要做好版本管理:

/**
 * 带版本号的订单创建事件
 * 向后兼容:新字段增加默认值,旧字段不删除
 */
public class OrderCreatedEvent implements DomainEvent {

    // 事件元数据
    private String eventId;
    private String eventType = "OrderCreated";
    private int version;         // 事件版本号
    private Instant occurredAt;

    // V1 字段
    private OrderId orderId;
    private CustomerId customerId;
    private Money totalAmount;

    // V2 新增字段(2025-03)
    private String channel;      // 下单渠道 (APP/WEB/MINI_PROGRAM)
    private String promotionCode; // 推广码

    // V3 新增字段(2025-06)
    private boolean isPresale;   // 是否预售
    private Instant expectedShipDate; // 预计发货日期

    // 构造方法
    private OrderCreatedEvent() {}  // 序列化用

    public static OrderCreatedEvent from(Order order) {
        OrderCreatedEvent event = new OrderCreatedEvent();
        event.eventId = UUID.randomUUID().toString();
        event.version = 3;  // 当前版本
        event.occurredAt = Instant.now();
        event.orderId = order.getId();
        event.customerId = order.getCustomerId();
        event.totalAmount = order.getTotalAmount();
        event.channel = order.getChannel();
        event.promotionCode = order.getPromotionCode();
        event.isPresale = order.isPresale();
        event.expectedShipDate = order.getExpectedShipDate();
        return event;
    }
}

// 消费者做版本兼容处理
@Component
public class InventoryHandler {

    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        // 按版本处理不同逻辑
        switch (event.getVersion()) {
            case 1:
                handleV1(event);
                break;
            case 2:
                handleV2(event);
                break;
            case 3:
                handleV3(event);
                break;
            default:
                log.warn("未知事件版本: {}", event.getVersion());
                // 尝试降级处理
                handleV1(event);
        }
    }

    private void handleV1(OrderCreatedEvent event) {
        // 处理 V1 逻辑(没有渠道、预售等信息)
    }
}

事件版本管理最佳实践

  1. 只增不减:新字段只添加,旧字段标记 @Deprecated 但不删除
  2. 消费者容错:未知字段忽略(不报错)
  3. 版本号递增:每次结构变更,版本号 +1
  4. 降级兼容:新消费者应能处理旧版本事件

第六部分:最终一致性处理

6.1 拥抱最终一致性

事件驱动架构的关键转变:从强一致性到最终一致性

强一致性(同步):
  订单创建 → 等待库存扣减完成 → 返回给用户
  用户等待时间 = 所有步骤耗时之和

最终一致性(异步):
  订单创建 → 立即返回给用户
  库存扣减(异步)→ 可能晚几秒完成
  用户等待时间 = 订单创建耗时

代价:用户可能看到"订单已创建,库存处理中..."的状态
收益:系统吞吐量提升 10 倍以上

6.2 处理失败重试

@Component
public class RobustInventoryHandler {

    private static final int MAX_RETRIES = 3;

    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
            try {
                inventoryService.deduct(event.getItems());
                return;  // 成功
            } catch (InsufficientInventoryException e) {
                // 库存不足 - 不重试,直接处理
                handleInsufficientInventory(event);
                return;
            } catch (TemporaryException e) {
                // 临时错误 - 重试
                log.warn("库存扣减临时失败,第 {} 次重试", attempt);
                sleep(1000L * attempt);  // 指数退避
            }
        }
        // 重试耗尽 - 发送到死信队列
        deadLetterQueue.send(event);
        alertService.alert("库存扣减失败", event);
    }
}

6.3 幂等性保障

事件可能被重复投递(at-least-once 语义),消费者必须实现幂等:

@Component
public class IdempotentInventoryHandler {

    @Autowired private EventProcessLogRepository logRepository;

    @EventListener
    @Transactional
    public void handleOrderCreated(OrderCreatedEvent event) {
        // 幂等检查:用 eventId 作为唯一键
        String eventId = event.getEventId();
        if (logRepository.existsByEventId(eventId)) {
            log.info("事件已处理,跳过: eventId={}", eventId);
            return;  // 幂等:已处理过
        }

        // 处理业务
        inventoryService.deduct(event.getItems());

        // 记录处理日志(插入成功 = 唯一索引保证不会重复处理)
        logRepository.save(new EventProcessLog(eventId, "PROCESSED", Instant.now()));
    }
}

// 数据库表设计
CREATE TABLE t_event_process_log (
    event_id VARCHAR(64) PRIMARY KEY,  -- 唯一约束 = 幂等保证
    status VARCHAR(20),
    processed_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

第七部分:EDA 的陷阱

7.1 事件风暴

症状:一个事件触发另一个事件,形成事件链,最终无法追踪数据流向:

OrderCreated → InventoryDeducted → StockLowAlert
                                → ReplenishmentRequested
                                → ReplenishmentApproved
                                → PurchaseOrderCreated
                                → ...

解决方案

  1. 事件溯源记录所有事件(虽然不能阻止风暴,但能追溯)
  2. 明确的事件所有权:谁能发布什么事件
  3. 事件流图:用文档/图表显式化事件依赖关系
  4. 断路保护:检测事件循环并熔断

7.2 调试困难

症状:一个请求分散在多个服务的多个事件中,像"大海捞针":

排查"为什么用户没收到库存扣减通知":
  1. 查订单服务的 order-created 事件是否发布
  2. 查库存服务是否消费了该事件
  3. 查 stock-low-alert 事件是否发布
  4. 查通知服务是否消费了 stock-low-alert 事件
  5. 查短信网关是否成功发送

解决方案

  • 全链路追踪:Jaeger/Zipkin 关联所有事件
  • 事件 ID 传递:在事件中携带 traceId
  • 事件追踪平台:可视化事件流
  • 每个事件携带 correlationId:串联同一个业务流程的所有事件
public class OrderCreatedEvent {
    private String eventId;
    private String traceId;       // 链路追踪 ID(从上游提取)
    private String correlationId; // 业务流程 ID(关联同一个流程)
    // ...
}

// 发布事件时传递 traceId
public void publish(Order order, String traceId) {
    OrderCreatedEvent event = OrderCreatedEvent.from(order);
    event.setTraceId(traceId);
    event.setCorrelationId(order.getOrderNo().toString());
    eventBus.publish(event);
}

7.3 数据不一致的排查

事件驱动下的数据不一致比同步调用更难排查,因为问题可能在你看到的时候已经"过去了"。

最佳实践

  • 监控事件延迟:如果事件处理延迟超过阈值,告警
  • 对账机制:定期对比源系统和目标系统的数据
  • 补偿任务:定期扫描"未完成"的业务流程并补偿
/**
 * 每天凌晨 2 点执行的对账任务
 */
@Component
public class DailyReconciliationJob {

    @Scheduled(cron = "0 0 2 * * ?")
    public void reconcile() {
        // 查询"订单已创建但库存未扣减"的异常数据
        List<Order> unreconciledOrders = orderRepository
            .findOrdersWithoutInventoryDeduction(
                LocalDate.now().minusDays(1));

        for (Order order : unreconciledOrders) {
            log.warn("发现未对账订单: {}", order.getId());
            // 补偿:重新发布库存扣减事件
            eventPublisher.publish(
                new InventoryDeductCompensationEvent(order));
        }
    }
}

第八部分:架构演进路线

从单体到事件驱动三步走

Phase 1: 内部事件
  在单体应用内使用 Spring Events (@EventListener)
  订单模块事件 → 通知模块、日志模块
  目标是理解事件思维,不引入外部依赖

Phase 2: 外部事件总线
  引入 Kafka/RocketMQ
  核心跨模块通信使用消息队列
  非核心仍保留同步调用

Phase 3: 完整 EDA
  所有模块间通信通过事件
  全链路追踪
  事件版本管理
  自动化对账和补偿

对于大多数项目,Phase 2 就是最优状态。Phase 3 适合事件驱动的产品型公司(如电商平台、金融交易系统)。


总结

概念要点
事件通知轻量,只发 ID,消费者回调查询
携带状态转移推荐:事件自包含,消费者无需回调
事件溯源不存状态存事件历史,适合审计
Command vs EventCommand=请求,Event=事实
消息选型大数据→Kafka,金融→RocketMQ,路由灵活→RabbitMQ
幂等靠 eventId + 唯一索引保证
版本管理只增不减,消费者容错
对账定期检查数据一致性,补偿异常

最终一致性不是 bug,是 feature。 拥抱它,用对账和监控来管理它,而不是试图消灭它。


参考资料

  1. Martin Fowler, "What do you mean by 'Event-Driven'?"
  2. Greg Young, "CQRS and Event Sourcing"
  3. Confluent, "Event-Carried State Transfer"
  4. Chris Richardson, "Pattern: Event-driven architecture"
  5. RocketMQ 官方文档 - 事务消息
上次编辑于:
贡献者: zhengtianqi