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 把这些概念串起来
好,咱们把这些概念串成一个完整的流程:
- 事件源(比如用户下单)产生一个事件
- 事件被发送到事件通道(比如 EventBridge)
- 事件通道根据事件模式进行匹配和路由
- 匹配成功的事件,被推送给对应的事件消费者(比如一个 Lambda 函数)
- 消费者处理事件,可能产生新的事件,继续这个循环
你想想看,这个流程里,每个环节都是松耦合的。事件源不知道消费者是谁,消费者不知道事件源是谁,通道只负责转发。这就是事件驱动架构的魅力所在。
一句话总结:事件定义是“契约”,事件源是“生产者”,事件通道是“管道”,事件模式是“过滤器”,事件消费者是“处理器”。五者缺一不可。
下一章,咱们会深入讲事件模式的具体写法,以及如何用 Serverless 服务实现这些模式。到时候我会拿实际项目中的案例来拆解,保证你听完就能用。