事件驱动架构:事件源与触发器、事件路由规则、事件重试与死信队列
事件驱动架构,说白了就是让系统里的各个模块通过「事件」来沟通。你想想看,传统的调用方式就像你打电话找人,对方必须在线。而事件驱动就像发微信,对方什么时候看都行,甚至可以把消息转发给其他人处理。
我在做 Serverless 项目时,发现很多团队一开始都习惯用同步调用。直到有一次线上出问题,一个下游服务挂了,整个调用链都堵死了。嗯,从那以后,我对事件驱动有了全新的认识。
事件源与触发器
事件源就是事件的「生产者」。在 Serverless 世界里,常见的事件源包括:
- API 网关:HTTP 请求触发函数
- 对象存储:文件上传、删除事件
- 数据库变更:增删改操作触发
- 消息队列:消息到达触发消费
- 定时器:按时间计划触发
触发器则是连接事件源和函数的「桥梁」。我个人习惯把触发器分为两类:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 同步触发器 | 等待函数返回结果 | API 请求、Webhook |
| 异步触发器 | 不等待结果,直接返回 | 日志处理、图片转码 |
我的经验:异步触发器更适合 Serverless。因为函数执行时间不确定,同步等待容易导致超时。我曾经把一个图片处理功能从同步改成异步,系统吞吐量提升了 3 倍。
事件路由规则
事件路由,就是决定「这个事件该交给谁处理」。说白了,它就是个智能分发系统。
常见的路由策略有:
- 基于内容路由:根据事件内容中的字段值,分发到不同处理函数
- 基于类型路由:根据事件类型(如 order.created、order.paid)分发
- 基于优先级路由:高优先级事件走快速通道
举个例子,假设我们有个电商系统:
// 事件路由配置示例
{
"routes": [
{
"eventType": "order.created",
"target": "order-validation-function",
"priority": "high"
},
{
"eventType": "order.paid",
"target": "inventory-update-function",
"priority": "normal"
},
{
"eventType": "order.shipped",
"target": "notification-function",
"priority": "low"
}
]
}
注意:路由规则不要写得太复杂。我曾经见过一个团队,路由规则嵌套了 5 层条件判断,结果出了问题根本查不清。保持简单,能用两层解决的别用三层。
事件重试与死信队列
事件处理不可能永远成功。网络抖动、下游服务不可用、代码 bug,这些都会导致处理失败。这时候就需要重试机制。
我建议的重试策略:
- 指数退避:第一次失败等 1 秒,第二次等 2 秒,第三次等 4 秒...
- 最大重试次数:一般设 3-5 次,别无限重试
- 重试间隔:至少 1 秒,避免打爆下游
那重试都失败了怎么办?这时候就该死信队列出场了。
死信队列(DLQ)就是事件的「收容所」。所有重试失败的事件,都会被扔到这里。你可以:
- 定期检查死信队列,分析失败原因
- 手动重新投递修复后的事件
- 设置告警,及时发现问题
核心原则:事件处理要「尽力而为」,但也要「适可而止」。重试 3 次还失败,就承认失败,把问题交给死信队列。别让一个坏事件拖垮整个系统。
我记得有一次线上事故,就是因为重试策略没设上限。一个事件处理失败后,系统疯狂重试,把数据库连接池打满了。最后所有服务都挂了。嗯,从那以后,我每个项目都会检查重试配置。
总结一下事件驱动架构的几个要点:
- 事件源负责产生事件,触发器负责连接函数
- 路由规则要清晰简单,别搞复杂
- 重试要有策略,死信队列要监控
- 异步处理是 Serverless 的天然优势
你想想看,如果每个模块都通过事件来通信,系统是不是就变得灵活多了?新增一个处理逻辑,只需要订阅对应的事件就行,完全不用改现有代码。这就是事件驱动的魅力所在。