2. 事件驱动架构 vs 传统架构:解耦与实时性的博弈

聊架构选型,绕不开一个核心问题:系统之间到底该怎么通信?

传统做法是请求-响应模型,你问我答。事件驱动模型则是,你发个消息,谁感兴趣谁处理。这两种思路,说白了就是「打电话」和「发朋友圈」的区别。

我这些年做过的项目,从几十万的内部工具到千万级 IoT 平台,几乎每次架构评审都会争论这个。今天我就把实战中的体会掰开揉碎,跟你聊聊。

2.1 请求-响应模型:经典的「你问我答」

这是大多数人最熟悉的模式。客户端发请求,服务端处理,返回结果。整个过程是同步的,调用方必须等。

典型场景:RESTful API、RPC 调用、数据库查询。

代码看起来像这样:

// 传统请求-响应:用户下单
public OrderResponse createOrder(OrderRequest request) {
    // 1. 校验库存
    boolean stockOk = stockService.checkStock(request.getProductId(), request.getQuantity());
    if (!stockOk) {
        return OrderResponse.fail("库存不足");
    }
    // 2. 扣减库存
    stockService.deductStock(request.getProductId(), request.getQuantity());
    // 3. 创建订单
    Order order = orderRepository.save(request.toOrder());
    // 4. 发送通知(同步阻塞)
    notificationService.sendOrderConfirmation(order);
    // 5. 返回结果
    return OrderResponse.success(order);
}

你看,每一步都是串行的。库存服务挂了,整个下单就失败。通知服务慢了,用户就得等着。

耦合度分析:

  • 调用方必须知道被调用方的地址、接口、参数格式
  • 被调用方的变更(比如接口升级)会直接导致调用方修改
  • 运行时强依赖:下游不可用,上游就报错

可扩展性瓶颈:

  • 扩展服务 B 时,服务 A 的代码通常要改(加新调用)
  • 流量突增时,整个调用链的每个环节都得跟着扩容
  • 我曾经维护过一个老系统,加一个通知渠道要改 6 个服务——改完还要全链路回归,痛苦得很

2.2 事件驱动模型:发布-订阅的「松耦合」

事件驱动的核心思想很简单:生产者只管发事件,不关心谁消费。消费者只管订阅,不关心谁生产。

中间有个事件总线(Message Broker)做解耦。

// 事件驱动:用户下单
public void createOrder(OrderRequest request) {
    // 1. 校验库存(本地校验或发送查询事件)
    // 2. 创建订单
    Order order = orderRepository.save(request.toOrder());
    // 3. 发布"订单已创建"事件
    eventBus.publish(new OrderCreatedEvent(order));
    // 4. 立即返回成功
    return OrderResponse.success(order);
}

// 其他服务各自订阅
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
    stockService.deductStock(event.getProductId(), event.getQuantity());
}

@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
    notificationService.sendOrderConfirmation(event.getOrder());
}

注意到区别了吗?下单服务不再直接调用库存和通知服务。它只发布一个事件,然后马上返回。库存扣减和通知发送是异步执行的。

耦合度对比:

维度 请求-响应 事件驱动
服务间依赖 编译期/运行时强依赖 仅依赖事件契约
接口变更影响 调用方必须同步修改 消费者可选升级
新增消费者 需修改生产者代码 只需订阅事件
故障隔离 下游故障直接导致上游失败 消费者故障不影响生产者

我的经验:事件驱动最爽的一点是「加功能不改老代码」。有一次产品要加个风控审核,我只需要新写一个服务订阅 OrderCreatedEvent,一行代码都没改下单服务。上线那天,我准时下班了。

2.3 实时性差异:同步 vs 异步的取舍

很多人一听到异步就觉得「慢」。其实这是个误解。

请求-响应:

  • 用户发起请求到收到响应,是端到端实时的
  • 适合需要立即知道结果的场景:登录、支付、查询
  • 但整个链路的延迟 = 所有服务延迟之和

事件驱动:

  • 生产者发布事件后立即返回,用户感知延迟低
  • 消费者处理是异步的,但通常毫秒级完成
  • 适合「最终一致性」可接受的场景:通知、日志、数据分析

举个例子:你在电商下单后,页面秒回「下单成功」。但积分到账、物流创建、短信通知可能几秒后才完成。用户感知是「快」,因为最慢的操作被异步化了。

注意:事件驱动不擅长强一致性场景。如果你需要「扣库存失败就不能创建订单」,那事件驱动需要额外实现补偿机制(如 Saga 模式)。我曾经在一个金融项目中硬用事件驱动做实时风控,结果补偿逻辑比主流程还复杂——后来老老实实改回了请求-响应。

2.4 一张图看懂两种架构

下面这张 SVG 图,我画了两种架构的核心流程对比。你看一眼就能明白本质区别。

请求-响应 vs 事件驱动架构对比 请求-响应模型 客户端 服务 A 服务 B 服务 C 请求 同步调用 同步调用 响应 响应 响应 事件驱动模型 客户端 服务 A 事件总线 (Message Broker) 服务 B 服务 C 请求 发布事件 订阅 订阅 立即返回 左侧:服务 A 同步等待 B、C 响应,耦合紧密。右侧:服务 A 发布事件后立即返回,B、C 异步消费,完全解耦。 事件总线承担了「中介」角色,生产者和消费者互不知晓对方存在。

2.5 选型建议:什么时候用哪个?

没有银弹。我个人的选型原则很简单:

  • 强一致性 + 低延迟要求 → 请求-响应。比如支付扣款、库存锁定。
  • 最终一致性可接受 + 需要解耦 → 事件驱动。比如通知推送、数据同步。
  • 混合架构 → 大部分项目其实都是两者结合。核心链路用请求-响应,非核心链路用事件驱动。

避坑指南:我曾经在一个项目中,把所有服务间调用都改成了事件驱动,觉得「解耦万岁」。结果调试问题时,事件流散落在 5 个 Broker 里,追踪一个请求要翻 3 个日志系统。后来我学乖了:关键路径用同步,扩展路径用异步。