3. 事件核心概念:事件定义、事件源、事件通道、事件消费者、事件模式

好,咱们进入正题。事件驱动架构里,有几个核心概念你必须吃透。说白了,不理解这些,后面设计系统就容易跑偏。我刚开始接触 Serverless 时,也在这上面栽过跟头,所以今天咱们一个一个掰开揉碎了讲。

3.1 事件定义:不只是“发生了什么”

事件是什么?很多人觉得就是“用户下单了”、“文件上传了”。嗯,对,但不全对。事件其实是一个状态变化的记录,它包含了上下文信息。

我个人习惯把事件定义成三个部分:

  • 事件头:谁产生的?什么时间?唯一 ID 是什么?
  • 事件体:具体发生了什么变化?数据长什么样?
  • 事件元数据:版本号、事件类型、来源系统等。

举个例子,一个“订单已支付”事件,不能只写个“paid: true”。你想想看,如果后续系统想知道支付金额、支付方式、用户 ID,这些信息从哪来?

重要原则:事件应该是自描述的。消费者拿到事件,不需要再去查数据库才能理解发生了什么。

{
  "eventId": "evt_20240315_001",
  "eventType": "order.paid",
  "source": "order-service",
  "time": "2024-03-15T10:30:00Z",
  "data": {
    "orderId": "ORD-12345",
    "userId": "USR-67890",
    "amount": 299.00,
    "paymentMethod": "wechat"
  }
}

我在项目中遇到过,有人把事件定义得特别“瘦”,就一个 ID。结果下游服务为了处理这个事件,得调五六个接口去拼数据。那性能,惨不忍睹。所以,事件定义要够用,但别冗余

3.2 事件源:谁在“搞事情”?

事件源,就是产生事件的那个实体。可以是用户操作、定时任务、外部系统回调,甚至是另一个事件处理后的结果。

常见的几种事件源:

  • 用户行为:点击按钮、提交表单、滑动页面
  • 系统内部:数据库变更、服务状态切换、定时任务触发
  • 外部系统:第三方 Webhook、消息队列推送、IoT 设备上报

这里有个坑,我提醒一下。事件源不应该关心谁消费了它的事件。它只管把事件发出去,至于谁收、怎么收,那是事件通道和消费者的事。这叫“解耦”。

我曾经见过一个项目,事件源里硬编码了消费者的地址。结果消费者一升级,事件源也得跟着改。这哪是事件驱动?这是“事件耦合”。

3.3 事件通道:事件的“高速公路”

事件通道,就是事件从源到消费者的传输管道。在 Serverless 世界里,最常见的通道就是消息队列和事件总线。

我个人常用的几种通道:

通道类型 特点 适用场景
消息队列(如 SQS) 点对点,保证至少一次投递 任务分发、异步处理
事件总线(如 EventBridge) 发布/订阅,支持过滤和路由 跨服务事件通知、复杂路由
流处理平台(如 Kafka) 高吞吐,持久化,支持回溯 日志采集、数据管道

你想想看,如果没有事件通道,事件源就得直接调用消费者。那如果消费者挂了怎么办?如果同时有十个消费者怎么办?通道的作用就是缓冲、解耦、削峰填谷

小技巧:选通道时,先问自己三个问题——我需要保证顺序吗?我需要重试吗?我需要广播给多个消费者吗?答案不同,选型就不同。

3.4 事件消费者:谁在“接招”?

消费者,就是处理事件的组件。在 Serverless 里,通常就是一个函数(Function)。

消费者有几个关键点要注意:

  • 幂等性:同一个事件处理两次,结果应该一样。因为消息队列可能会重复投递。
  • 无状态:消费者不应该依赖本地内存或文件系统。每次调用都是独立的。
  • 快速响应:消费者处理时间越短越好。如果处理时间长,考虑拆成多个小事件。

我记得有一次,一个消费者处理事件时调了个外部 API,结果那个 API 超时了。消费者一直等,最后超时被平台杀掉。事件没处理完,又重试,又超时……死循环了。后来我改成异步回调模式,才解决。

核心原则:消费者要“快进快出”。如果必须做耗时操作,把结果再发一个新事件,让另一个消费者去处理。

3.5 事件模式(Event Pattern):事件的“路由规则”

事件模式,说白了就是如何匹配和路由事件。不是所有事件都需要被所有消费者处理。我们需要一种机制,让消费者只关心它感兴趣的事件。

常见的模式匹配方式:

  • 基于事件类型:比如只处理 order.paid 类型的事件
  • 基于事件源:比如只处理来自 payment-service 的事件
  • 基于事件内容:比如只处理金额大于 1000 的支付事件
  • 组合模式:多个条件组合,比如“来自 order-service 且类型是 order.cancelled”

在 AWS EventBridge 里,事件模式是用 JSON 表达的:

{
  "source": ["order-service"],
  "detail-type": ["order.paid"],
  "detail": {
    "amount": [{ "numeric": [">=", 1000] }]
  }
}

这个模式的意思是:只匹配来自 order-service、类型是 order.paid、且金额大于等于 1000 的事件。其他事件?直接忽略。

我的经验:事件模式别写得太复杂。太复杂的模式,调试起来很痛苦。我曾经写过一个嵌套五层的模式,结果线上跑了一个月才发现有个条件写错了。嗯,从那以后,我坚持“模式越简单越好”。

3.6 把这些概念串起来

好,咱们把这些概念串成一个完整的流程:

  1. 事件源(比如用户下单)产生一个事件
  2. 事件被发送到事件通道(比如 EventBridge)
  3. 事件通道根据事件模式进行匹配和路由
  4. 匹配成功的事件,被推送给对应的事件消费者(比如一个 Lambda 函数)
  5. 消费者处理事件,可能产生新的事件,继续这个循环

你想想看,这个流程里,每个环节都是松耦合的。事件源不知道消费者是谁,消费者不知道事件源是谁,通道只负责转发。这就是事件驱动架构的魅力所在。

一句话总结:事件定义是“契约”,事件源是“生产者”,事件通道是“管道”,事件模式是“过滤器”,事件消费者是“处理器”。五者缺一不可。

下一章,咱们会深入讲事件模式的具体写法,以及如何用 Serverless 服务实现这些模式。到时候我会拿实际项目中的案例来拆解,保证你听完就能用。