2.3 事件驱动架构与 Serverless


2.3 事件驱动架构与 Serverless

本节摘要:事件驱动是 Serverless 的灵魂——组件之间不直接调用,而是通过"事件"来通信。本节讲清它和传统请求-响应模型的区别、事件怎么在函数与 BaaS 之间流转、同步与异步的边界,以及编排复杂流程时怎么做。

学习目标

阅读完本节,你应当能够:

  1. 区分事件驱动和请求-响应两种模型
  2. 描述一个事件从产生到被处理的完整流程
  3. 判断某步该用同步调用还是异步事件
  4. 理解事件编排(工作流)解决什么问题

一、从"我调用你"到"事件唤醒我"

传统架构里,服务之间是直接调用的:A 服务同步调用 B 服务的接口,等 B 返回了 A 才继续。这种方式直观,但耦合紧——A 必须知道 B 在哪、B 必须在线、B 慢了 A 就跟着慢。

事件驱动架构换了个思路:组件之间不直接调用,而是把"发生了什么"作为事件发出去,谁关心谁订阅。A 只管把"订单已创建"这个事件发出去就完事,至于后续谁去发邮件、谁去更新库存、谁去通知物流,A 既不关心也不等待。

Serverless 天生契合事件驱动:函数本来就不是常驻服务,而是"被事件唤醒"的。事件驱动让一堆短小的函数通过事件串联成一个完整系统,每个函数只做一件小事,互相解耦。

二、一个事件的完整旅程

以"用户上传图片"为例,跟着一个事件走一遍:

用户上传图片到对象存储;存储发出"对象创建"事件到事件总线;事件总线按订阅规则把这个事件分发给关心它的函数 A 和函数 B;两个函数各自处理,互不干扰、并行进行。整个过程里,没有一个组件在"等待"另一个——这就是事件驱动的解耦之美。

再把"至少一次投递、失败重试、死信兜底"这些分布式语义也画进来,才是生产级的事件流转全貌:

图:事件驱动流转全景(含重试与死信)

图:事件驱动流转全景(含重试与死信)

注意几个关键点:

  • 事件源:产生事件的组件(对象存储、数据库、HTTP 网关、定时器)。
  • 事件总线/队列:负责接收、路由、分发事件的中枢 BaaS。
  • 消费者:被事件触发的函数。
  • 幂等性:事件可能被重复投递(至少一次语义),函数处理必须幂等——同一个事件处理多次结果一致。

⚠️ 重要坑:一定要写幂等函数。事件驱动下,同一事件可能被重试或重复触发,如果你的函数"扣款"不是幂等的,重复触发就会扣两次。靠业务唯一键去重、用状态机标记"已处理"都是常见手段。

三、同步还是异步:边界在哪

虽然事件驱动是主流,但不是所有场景都该异步。两条简单原则:

该同步的场景:客户端在等结果,且处理很快。比如"查询订单详情"——用户在屏幕前等,函数几百毫秒内能返回,就该同步(HTTP 请求 → 网关 → 函数 → 直接返回)。

该异步的场景:客户端不等、或者处理较慢、或者要触发多个下游。比如"下单后发邮件、更新库存、通知物流"——用户不需要等这些全部完成,就该把订单创建作为事件发出去,让下游函数异步各干各的。

💡 判断口诀用户在不在等。在等且能快——同步;不等或快不了——异步。

四、复杂流程:用编排而非互调

当一个业务流程要串起很多步(下单 → 扣库存 → 支付 → 发货 → 通知),如果让函数之间互相直接触发,会变成一团乱麻,出错难追溯。这时该用工作流编排服务(如 Step Functions、Durable Functions)。

编排服务扮演"指挥家":你定义好"先做 A,A 成功做 B,B 失败做 C"这样的流程,由编排服务负责按顺序触发各函数、传递中间结果、处理失败重试、保存流程状态。函数自己仍然无状态,"流程进行到哪一步"这种状态由编排服务持有。

💡 关键直觉:编排服务让"有状态的复杂流程"在"无状态的函数"之上成为可能。流程状态外置到编排服务,就像业务数据外置到数据库一样——都是 Serverless"状态外置"思想的延伸。

五、事件设计的演化与经验

事件驱动不是 Serverless 发明的概念——企业消息总线、发布订阅模式比 FaaS 早了二十年——但 Serverless 把它从"大型集成的架构选择"变成了"默认的粘合方式",也因此在事件设计上沉淀出一批新的经验教训。

事件粒度的演化。 早期实践爱发"细粒度通知"事件(字段改了、记录插入了),下游函数各自判断要不要管。很快人们发现这种设计让系统变成侦探小说——每个函数都要过滤大量不相关事件,事件流量本身成了成本和延迟来源。演化方向是业务语义事件:不发"订单表更新了",发"订单已支付"。前者是数据层的涟漪,后者是业务层的里程碑,订阅者一看事件名就知道与自己有关。经验法则:事件名用"业务对象 + 完成的动作"命名,讲不清楚这个动作对业务意味着什么,说明粒度或命名有问题。

事件契约的稳定性。 事件一旦发出去就有了订阅者,改字段就是给别人埋雷——上游不知道谁在听,删掉一个"没人用"的字段常常在三个月后引爆某个遗忘的消费者。成熟团队的做法是给事件立版本(新版本新主题、旧版本给迁移期)、文档化 schema、把"废弃预告"当成正式流程。这套纪律在传统架构里也该有,只是事件的异步性让破坏更隐蔽、爆雷更延迟,纪律因此更重要。

可观测性是事件架构的命门。 同步调用链路里一次请求的轨迹天然连续;事件链路里"下单函数发事件、库存函数两秒后被触发",中间断开了。没有统一的关联标识(trace id 随事件透传),排障时你要在多个服务的日志里靠时间戳猜因果。第 4 章实践清单里"从第一天就上链路追踪"这条,多数教训正是来自事件链路的排障之痛。

六、一个反模式案例:同步链上叠异步

用一个真实的失败模式收尾。某团队做下单接口:函数 A 校验后同步调用函数 B 扣库存,B 再发事件给 C 更新统计。上线初期一切正常,大促时雪崩了——A 同步等 B,B 的实例被平台限流,A 大量超时,而 C 收到的事件里有相当比例来自"最终失败但已发出去"的订单,统计数据错乱。

复盘出两条教训。其一,同步链的长度就是脆弱度:每一环的失败、限流、冷启动都直接叠加到用户等待上,A 与 B 这种强耦合的调用要么合并成一个函数,要么彻底异步化,"半同步半异步"的混合链是最差选择。其二,事件的发出时机要跟业务确认点对齐:B 还没确认扣减成功就发事件,等于广播了未发生的事实。修正后的结构是:A 一体化完成校验与扣减,确认成功后才发"订单已支付"事件,C 只消费既成事实。这个案例几乎浓缩了本节全部要点——同步异步的边界、事件即事实、幂等与补偿——值得在学习第 4 章开发流程前反复回味。

本节要点回顾

  • 事件驱动:组件不直接调用,靠事件通信,天然解耦、契合 Serverless。
  • 一个事件旅程:事件源 → 事件总线/队列 → 消费函数
  • 函数必须幂等——事件可能重复投递。
  • 同步 vs 异步看"用户在不在等":在等且快就同步,否则异步。
  • 复杂流程用编排服务(Step Functions 等)串联,状态外置给编排器,函数保持无状态。

至此核心概念讲完。下一章横向看 Serverless 的技术栈和各厂商平台,帮你做选型。

五、事件设计的三个成熟模式

事件驱动不只是"用消息连起来",事件本身的设计有成熟模式可循。模式一,通知事件与携带事件分离:通知事件只带"发生了什么加资源定位"(订单已创建, ID 是多少),消费者按需回查详情;携带事件把完整数据塞进消息体,省一次回查但耦合了 schema——高频与松耦合场景用前者,低频与消费端多样化时后者划算。模式二,版本化与兼容演进:事件 schema 加字段可以、改字段语义不行、删字段要有弃用期——消费者多的体系里,事件契约的管理严格程度不亚于公开 API。模式三,幂等与去重的一等公民地位:至少一次投递是常态,消费者幂等是义务——设计期就定好幂等键(事件 ID 加业务键),把去重逻辑做成基础设施而不是每个消费者各写一套。三个模式的共同指向:事件驱动架构的成熟度不取决于你用了多高级的总线,而取决于事件契约的治理水平——契约乱糟糟的体系,总线再先进也是一堆优雅传输的垃圾。

补最后一个契约治理工具:事件目录——把体系里所有事件(名称、生产者、消费者、schema 版本、负责人)登记成册,变更走目录公告——没有目录的事件体系,半年后就没人说得清"谁在听我的事件";有了目录,删字段前的"广播通知"才有明确的收件人列表。目录可以简陋(一张表),但不能缺席——它和代码里的事件常量一起,构成事件驱动架构的"户籍制度"。

最后补一个调试技巧:事件驱动链路的本地断点调试几乎不可能(触发在云端),务实的替代是"重放开发"——把生产的事件样本(脱敏后)存档,本地与测试环境用重放工具逐条回放;这个习惯既解决调试问题,又顺手建立了回归测试的事件库——一箭双雕的工程实践,值得进团队的模板仓库。

再补一条与成本章的联动:事件驱动不只是架构风格,也是成本结构——事件粒度越细、链路越长,请求数越多账单越碎;设计期的事件粒度(一笔订单一个事件还是每个状态变化一个事件)要同时考虑消费端的灵活性与计费侧的调用量——粒度是弹性与成本的权衡点之一,架构师手里的每个决策都在同时写两份文档:一份代码一份账单。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U