EDA 事件驱动架构从入门到精通,老师不让外传的内部笔记

1 开头想讲个故事

2019 年双 11,阿里巴巴创下了每秒 54.4 万笔交易的峰值记录。

这个数字意味着什么?假设你手速足够快,每秒能完成一次交易,也要不吃不喝不睡觉地连续工作 15年 才能达到这个量级。

他们是怎么做到的?

答案绝对不是"买更多服务器"那么简单。传统架构下,订单进来后需要依次调用:库存服务 -> 支付服务 -> 物流服务 -> 通知服务...任何一个环节卡主,整个链路就崩溃。

而大厂用的是另一套玩法

当你下单成功后,系统知识轻描淡写地"发布"了一个【订单已创建】的时间,然后就告诉你"提交成功"了。后续的库存检查、仓库备货、物流通知、短信推送...全由各个服务异步完成,互不干扰。

这就是**事件驱动架构(Event-Driven Architecture,简称 EDA)**的威力。

2 EDA 到底是什么?

2.1 重新定义【事件】

先跑掉教科书上哪些晦涩难懂的定义。用最直白的话说:

事件 = 系统中发生的任何事情的事件记录

你下单了,是一个事件。

库存减扣了,是一个事件。

用户登录了,是一个事件。

温度传感器读数变化了,也是一个事件。

TypeScript 复制代码
/**
 * 订单创建事件 - 就是一个"事实记录"
 * @interface OrderCreatedEvent
 * @description 记录"订单已经创建"这个事实
 */
interface OrderCreatedEvent {
  eventId: string;           // 事件的唯一身份证
  eventType: 'OrderCreated'; // 事件类型
  orderId: string;           // 哪个订单
  userId: string;            // 哪个用户
  totalAmount: number;       // 多少钱
  occurredAt: string;       // 什么时候发生的
}

2.2 事件 vs 命令:80% 的人都搞混了

这是 EDA 最核心的概念,90% 的面试者都答不清楚。

命令:

**一句话定义 -**帮我做一件事

发送方式- 点对点,我只告诉特定的人

可以撤回吗?- 可以,我还没做呢!

举例 - 创建订单

事件:

一句话定义- 这件事已经发生了

发送方式 - 广播,谁想听都可以过来听

可以撤回吗? - 不行,已经发生了

举例- 订单已创建

TypeScript 复制代码
/**
 * 命令:我要你做事(可能做也可能不做)
 */
interface CreateOrderCommand {
  type: 'CreateOrderCommand';
  payload: { userId: string; items: CartItem[] };
  // 接收者需要回复:成功还是失败
}
TypeScript 复制代码
/**
 * 事件:向大家宣告一件事(不可撤销的历史事实)
 */
interface OrderCreatedEvent {
  type: 'OrderCreated';
  payload: { orderId: string; userId: string; totalAmount: number };
  // 谁爱听谁听,跟我无关
}

2.3 EDA 的三大核心角色

发布者(Publisher):只负责"广播"发生了什么,不关心谁会处理

事件总线(Event Bus):负责传递、十九话、保证送达

消费者(Consumer):订阅自己关心的事情,独自处理

3 传统架构 vs 事件驱动

3.1 你一定见过的【同步调用地狱】

传统同步调用 - 一个请求要经过整条链路

TypeScript 复制代码
class OrderService {
  async createOrder(userId: string, items: Item[]): Promise<Order> {
    // 步骤1:创建订单(耗时 10ms)
    const order = await this.orderRepo.create({ userId, items });
    
    // 步骤2:扣减库存(等待...如果库存服务慢呢?)
    await this.inventoryService.reserveStock(items);      // 🔸 阻塞
    
    // 步骤3:处理支付(等待...如果支付超时呢?)
    await this.paymentService.process(order);             // 🔸 阻塞
    
    // 步骤4:通知仓库(等待...)
    await this.warehouseService.notify(order);            // 🔸 阻塞
    
    // 步骤5:发送短信(等待...)
    await this.smsService.send(order.userId);             // 🔸 阻塞
    
    return order;  // 用户等了 500ms+,体验极差
  }
}

这种架构的问题:

  • 一个服务挂 -> 整条链路挂
  • 用户要等所有步骤完成才能收到响应
  • 添加新功能必须修改原有代码
  • 任何一个环节慢,整条响应就慢

3.2 事件驱动:优雅的异步解耦

TypeScript 复制代码
class OrderService {
  constructor(private eventBus: EventBus) {}

  /**
   * 下单操作 - 立即返回,不需要等待后续处理
   */
  async createOrder(userId: string, items: Item[]): Promise<Order> {
    const order = await this.orderRepo.create({ userId, items });
    
    // 只需要发布一个事件,然后...就没有然后了
    await this.eventBus.publish('order.created', {
      orderId: order.id,
      userId,
      items,
      totalAmount: order.totalAmount
    });
    
    return order;  // 用户立即收到响应,耗时 < 50ms
  }
}

/**
 * 库存服务 - 自己监听事件,自己处理
 */
class InventoryConsumer {
  constructor(private eventBus: EventBus, private inventory: InventoryService) {
    this.eventBus.subscribe('order.created', this.handle);
  }

  async handle(event: OrderCreatedEvent) {
    await this.inventory.reserveStock(event.items);
    // 处理完成,发个事件通知别人
    await this.eventBus.publish('stock.reserved', { orderId: event.orderId });
  }
}

/**
 * 通知服务 - 完全独立,爱谁谁
 */
class NotificationConsumer {
  constructor(private eventBus: EventBus, private notifier: Notifier) {
    this.eventBus.subscribe('order.created', this.handle);
  }

  async handle(event: OrderCreatedEvent) {
    await this.notifier.sendSMS(event.userId, '订单创建成功');
  }
}

/**
 * 支付服务 - 也独立订阅
 */
class PaymentConsumer {
  constructor(private eventBus: EventBus, private payment: PaymentService) {
    // 支付服务可能需要等库存确认后再处理
    this.eventBus.subscribe('stock.reserved', this.handle);
  }

  async handle(event: StockReservedEvent) {
    await this.payment.process(event.orderId);
  }
}

3.3 对比总结

|------|-------------|------------|
| 维度 | 传统同步架构 | 事件驱动架构 |
| 响应时间 | 所有步骤耗时总和 | 只算第一步耗时 |
| 服务依赖 | 强耦合,一个挂,全都挂 | 松耦合,互不影响 |
| 扩展性 | 难,一个慢扩展哪个 | 简单,哪个慢扩展哪个 |
| 新增功能 | 修改源代码 | 新增消费者即可 |
| 故障影响 | 链路崩溃 | 单点故障局部影响 |
| 用户体验 | 等待时间长 | 响应速度快 |

4 为什么大厂都在用 EDA?四大核心优势

4.1 松耦合(Decoupling)

这是 EDA 最大的价值,没有之一

发布者和消费者之间完全不依赖彼此的存在

TypeScript 复制代码
/**
 * 订单服务:只管发布事件,不关心谁在听
 */
class OrderService {
  async publish(order: Order) {
    await this.eventBus.publish('order.created', order.toEvent());
    
    // 将来如果要加"数据分析服务"?
    // 不好意思,不关订单服务的事,你自己订阅去
  }
}

/**
 * 某天产品说:我们要给用户发优惠券
 * 工程师:好,加一个优惠券消费者就行,订单服务代码不用动一根汗毛
 */
class CouponConsumer {
  constructor(eventBus: EventBus) {
    eventBus.subscribe('order.created', async (event) => {
      // 判断是否满足优惠券条件
      if (event.totalAmount > 100) {
        await this.couponService.grantCoupon(event.userId, 'NEW_USER_100');
      }
    });
  }
}

现实比喻

传统架构:老板站在你身后盯着你干活

事件驱动:老板发个任务单就不管了,自己安排时间完成。

4.2 可扩展性(Scalability)

每个消费者可以独立扩展,想加多少加多少

TypeScript 复制代码
/**
 * 消费者组 - 支持水平扩展
 * @description 流量高峰期,多开几个消费者实例就完事了
 */
class ConsumerGroup<T> {
  private consumers: Consumer<T>[] = [];
  
  /**
   * 根据负载动态扩展
   */
  scaleOut(count: number): void {
    for (let i = 0; i < count; i++) {
      this.consumers.push(new Consumer(this.eventBus, this.handler));
    }
    console.log(`已扩展到 ${this.consumers.length} 个消费者实例`);
  }
  
  /**
   * 流量低了缩回来
   */
  scaleIn(count: number): void {
    this.consumers.splice(0, count);
    console.log(`已缩减到 ${this.consumers.length} 个消费者实例`);
  }
}

4.3 容错与韧性(Resilience)

消息持久化 + 重试机制 = 永不丢失

TypeScript 复制代码
/**
 * 可靠的事件处理 - 消息不丢失
 * @class ReliableEventBus
 */
class ReliableEventBus {
  private queue: MessageQueue;
  
  /**
   * 发布时自动持久化
   */
  async publish(topic: string, payload: object): Promise<void> {
    const message = {
      id: uuid(),
      topic,
      payload,
      timestamp: new Date().toISOString(),
      retryCount: 0
    };
    
    // 写磁盘/数据库,确保不丢
    await this.queue.persist(message);
    await this.queue.route(topic, message);
  }
  
  /**
   * 消费失败自动重试
   */
  async subscribe(topic: string, handler: Handler): Promise<void> {
    await this.queue.listen(topic, async (message) => {
      try {
        await handler(message.payload);
        await message.ack();  // 确认处理成功
      } catch (error) {
        // 重试机制
        if (message.retryCount < 3) {
          message.retryCount++;
          await this.queue.retry(message, 1000 * message.retryCount);  // 延迟重试
        } else {
          // 进入死信队列,人工处理
          await this.queue.deadLetter(message, error);
        }
      }
    });
  }
}

4.4 实时响应(Real-time)

事件驱动天然支持实时处理,毫秒级响应

TypeScript 复制代码
/**
 * 股票价格实时监控 - 事件驱动的典型应用
 */
class StockPriceMonitor {
  constructor(private eventBus: EventBus) {
    this.eventBus.subscribe('stock.price.updated', this.handle);
  }

  async handle(event: PriceUpdateEvent) {
    // 1. 实时检查告警(毫秒级)
    if (Math.abs(event.changePercent) > 10) {
      await this.sendAlert(event);
    }
    
    // 2. 实时更新缓存
    await this.cache.set(event.symbol, event.price);
    
    // 3. 实时推送客户端
    await this.wsBroadcast(event.symbol, event);
  }
}

5 EDA 三大核心模式

5.1 发布 / 订阅(Pub / Sub)

最基础,也是最常用的模式

TypeScript 复制代码
/**
 * 简单的 Pub/Sub 实现
 */
class PubSub {
  private subscriptions = new Map<string, Handler[]>();
  
  subscribe(topic: string, handler: Handler): void {
    if (!this.subscriptions.has(topic)) {
      this.subscriptions.set(topic, []);
    }
    this.subscriptions.get(topic)!.push(handler);
  }
  
  async publish(topic: string, data: any): Promise<void> {
    const handlers = this.subscriptions.get(topic) || [];
    await Promise.all(handlers.map(h => h(data)));
  }
}

5.2 事件溯源(Event Sourcing)

不存当前状态,存放所有历史事件

TypeScript 复制代码
/**
 * 银行账户 - 事件溯源示例
 * @class BankAccount
 * @description 不存储余额,只存储所有交易记录
 */
class BankAccount {
  private events: AccountEvent[] = [];
  
  /**
   * 存款 - 其实是记录一个"存款事件"
   */
  deposit(amount: number): void {
    this.events.push({
      type: 'DEPOSIT',
      amount,
      timestamp: new Date().toISOString()
    });
  }
  
  /**
   * 取款 - 记录一个"取款事件"
   */
  withdraw(amount: number): void {
    if (this.getBalance() < amount) {
      throw new Error('余额不足');
    }
    this.events.push({
      type: 'WITHDRAWAL',
      amount,
      timestamp: new Date().toISOString()
    });
  }
  
  /**
   * 当前余额 = 重放所有事件
   */
  getBalance(): number {
    return this.events.reduce((balance, event) => {
      return event.type === 'DEPOSIT' 
        ? balance + event.amount 
        : balance - event.amount;
    }, 0);
  }
  
  /**
   * 完整的历史记录 - 审计追踪了解一下?
   */
  getHistory(): AccountEvent[] {
    return [...this.events];
  }
}

事件溯源的超级优势:

  • 完整的审计日志(每一笔钱怎么来的,清清楚楚)
  • 可以回到任意时间点的状态(时间旅行调试)
  • 可以重放时间来修复数据错误

5.3 CQRS(命令查询职责分离)

读和写,用不同的模型

CQRS模式

写模型:负责创建、更新

读模式:负责查询,针对不同场景优化

TypeScript 复制代码
// -------------------- 写端 --------------------
class OrderCommandHandler {
  async handle(cmd: CreateOrderCommand): Promise<void> {
    const order = new Order(cmd.userId, cmd.items);
    await this.orderRepo.save(order);           // 存订单
    await this.eventBus.publish('order.created', order.toEvent()); // 通知读端
  }
}

// -------------------- 读端 --------------------
class OrderQueryHandler {
  // 场景1:查订单详情(归一化视图)
  async getOrderDetail(id: string): Promise<OrderDetail> {
    return this.detailView.get(id);
  }
  
  // 场景2:查用户的所有订单(反规范化视图)
  async getUserOrders(userId: string): Promise<OrderSummary[]> {
    return this.userOrderView.getByUserId(userId);
  }
  
  // 场景3:运营后台统计(聚合视图)
  async getStatistics(): Promise<OrderStats> {
    return this.statsView.get();
  }
}

// -------------------- 视图同步 --------------------
class ViewSyncHandler {
  constructor(private eventBus: EventBus) {
    this.eventBus.subscribe('order.created', this.syncDetailView);
    this.eventBus.subscribe('order.created', this.syncUserOrderView);
    this.eventBus.subscribe('order.created', this.syncStatsView);
  }
  
  // 更新各种读视图,互不影响
}

6 大厂怎么使用 EDA

6.1 电商订单全流程

6.2 物联网数据处理

TypeScript 复制代码
/**
 * 传感器数据实时处理流水线
 */
class IoTPipeline {
  constructor(private eventBus: EventBus) {
    // 温度传感器数据处理
    this.eventBus.subscribe('sensor.temperature', this.handleTemperature);
    
    // 湿度传感器数据处理
    this.eventBus.subscribe('sensor.humidity', this.handleHumidity);
    
    // 异常告警处理
    this.eventBus.subscribe('sensor.anomaly', this.handleAnomaly);
  }

  async handleTemperature(event: TemperatureEvent) {
    // 1. 存时序数据库
    await this.tsdb.insert(event);
    
    // 2. 超温告警
    if (event.value > 80) {
      await this.eventBus.publish('sensor.anomaly', {
        sensorId: event.sensorId,
        type: 'HIGH_TEMPERATURE',
        value: event.value,
        threshold: 80
      });
    }
    
    // 3. 更新实时仪表盘
    await this.dashboard.update(event.sensorId, event.value);
  }
}

7 EDA 四大挑战及解决方案

7.1 最终一致性

**问题:**事件驱动是异步的,如果保证数据最终一致性?

**解决方案:**Saga 模式

Saga 模式 能够把分布式事务拆分成多个本地事务

TypeScript 复制代码
class OrderSaga {
  async execute(cmd: CreateOrderCommand): Promise<void> {
    try {
      // 步骤1:创建订单
      const order = await this.orderService.create(cmd);
      
      // 步骤2:扣库存(失败?回滚订单)
      await this.inventoryService.reserve(cmd.items);
      
      // 步骤3:扣款(失败?回滚库存)
      await this.paymentService.charge(order);
      
      // 步骤4:完成
      await this.eventBus.publish('order.completed', order);
    } catch (error) {
      // 补偿事务:哪里出错就回滚哪几步
      await this.compensate(cmd);
    }
  }
}

7.2 事件顺序

**问题:**时间可能乱序到达怎么办?

**解决方式:**分区 + 序列号

方案1,同一个对象的时间走同一个分区

方案2:消费者端按顺序处理

TypeScript 复制代码
/**
 * 保证事件顺序
 */
class OrderedConsumer {
  // 方案1:同一对象的事件走同一分区
  async consume(event: Event): Promise<void> {
    const partitionKey = event.orderId; // 同一个订单的事件进同一个分区
    await this.queue.consume('orders', partitionKey, this.handler);
  }
  
  // 方案2:消费者端排序
  private eventBuffer = new Map<string, Event[]>();
  
  async handle(event: Event): Promise<void> {
    const events = this.eventBuffer.get(event.orderId) || [];
    events.push(event);
    events.sort((a, b) => a.sequence - b.sequence); // 按序号排序
    
    // 按顺序处理
    for (const e of events) {
      await this.process(e);
    }
  }
}

7.3 幂等性

**问题:**消费者可能被调用多次,如何保证不重复消费?

**解决方案:**去重机制

TypeScript 复制代码
class IdempotentConsumer {
  private processed = new Set<string>();
  
  async handle(event: Event): Promise<void> {
    // 已经处理过?直接跳过
    if (this.processed.has(event.id)) {
      console.log(`事件 ${event.id} 已处理,跳过`);
      return;
    }
    
    // 处理业务
    await this.businessLogic(event);
    
    // 记录已处理
    this.processed.add(event.id);
    
    // 生产环境应该存数据库,用唯一键约束
    // await this.eventLog.save({ eventId: event.id, processedAt: new Date() });
  }
}

7.4 调试困难

**问题:**事件驱动系统如何追踪问题?

**解决方案:**分布式追踪

TypeScript 复制代码
/**
 * 事件链路追踪
 */
class TracedEventBus {
  async publish(topic: string, payload: object): Promise<void> {
    const tracing = {
      traceId: uuid(),        // 全链路ID
      spanId: uuid(),        // 当前Span
      parentId: getCurrentSpanId(),
      timestamp: Date.now()
    };
    
    await this.bus.publish(topic, { ...payload, _trace: tracing });
  }
  
  subscribe(topic: string, handler: Handler): void {
    this.bus.subscribe(topic, async (event) => {
      const span = this.tracer.startSpan(handler.name, {
        traceId: event._trace.traceId,
        parentId: event._trace.spanId
      });
      
      try {
        await handler(event);
        span.setTag('status', 'ok');
      } catch (e) {
        span.setTag('status', 'error');
        span.log({ error: e.message });
      } finally {
        span.end();
      }
    });
  }
}

8 总结:什么时候该用 EDA?

EDA 适用场景:

  • 高并发系统,需要处理海量请求
  • 微服务架构,服务间需要解耦
  • 实时处理,需要毫秒级响应
  • 审计追踪,需要完整的事件历史
  • 业务流程复杂,多系统多步骤协作

慎用 EDA:

  • 简单单体应用,没必要增加复杂度
  • 强一致性要求,金融转账等场景
  • 团队不熟悉,学习曲线需要了解一下

9 最后的建议

事件驱动架构 ≠ 不用学任何东西

它带来了灵活性 ,但也带来了**复杂性。**在采用之前,请确保:

  • 你的团队能驾驭,需要理解异步解耦、消息队列、分布式事务
  • 你的场景合适,不是所有系统都需要 EDA
  • 你准备好了监控,事件驱动系统的问题更难排查

但一旦用好,EDA 就是你系统最坚强的后盾。

相关推荐
读研的武3 小时前
Golang学习笔记 入门篇
笔记·学习·golang
自传丶3 小时前
【学习笔记】大模型应用开发系列(二)Embedding 模型
笔记·学习·embedding
左左右右左右摇晃4 小时前
Java 对象:创建方式与内存回收机制
java·笔记
JMchen1234 小时前
企业级图表组件库完整实现
android·java·经验分享·笔记·canvas·android-studio
ALKAOUA13 小时前
力扣面试150题刷题分享
javascript·笔记
無限進步D13 小时前
Java 循环 高级(笔记)
java·笔记·入门
左左右右左右摇晃13 小时前
Spring + SpringMVC 面试题整理笔记(二)
笔记
今天你TLE了吗13 小时前
JVM学习笔记:第八章——执行引擎
java·jvm·笔记·后端·学习
左左右右左右摇晃14 小时前
Spring Boot + Vue 实现文件上传下载
笔记