本节摘要:事件驱动是 Serverless 的灵魂——组件之间不直接调用,而是通过"事件"来通信。本节讲清它和传统请求-响应模型的区别、事件怎么在函数与 BaaS 之间流转、同步与异步的边界,以及编排复杂流程时怎么做。
阅读完本节,你应当能够:
传统架构里,服务之间是直接调用的:A 服务同步调用 B 服务的接口,等 B 返回了 A 才继续。这种方式直观,但耦合紧——A 必须知道 B 在哪、B 必须在线、B 慢了 A 就跟着慢。
事件驱动架构换了个思路:组件之间不直接调用,而是把"发生了什么"作为事件发出去,谁关心谁订阅。A 只管把"订单已创建"这个事件发出去就完事,至于后续谁去发邮件、谁去更新库存、谁去通知物流,A 既不关心也不等待。
Serverless 天生契合事件驱动:函数本来就不是常驻服务,而是"被事件唤醒"的。事件驱动让一堆短小的函数通过事件串联成一个完整系统,每个函数只做一件小事,互相解耦。
以"用户上传图片"为例,跟着一个事件走一遍:
用户上传图片到对象存储;存储发出"对象创建"事件到事件总线;事件总线按订阅规则把这个事件分发给关心它的函数 A 和函数 B;两个函数各自处理,互不干扰、并行进行。整个过程里,没有一个组件在"等待"另一个——这就是事件驱动的解耦之美。
再把"至少一次投递、失败重试、死信兜底"这些分布式语义也画进来,才是生产级的事件流转全貌:

注意几个关键点:
⚠️ 重要坑:一定要写幂等函数。事件驱动下,同一事件可能被重试或重复触发,如果你的函数"扣款"不是幂等的,重复触发就会扣两次。靠业务唯一键去重、用状态机标记"已处理"都是常见手段。
虽然事件驱动是主流,但不是所有场景都该异步。两条简单原则:
该同步的场景:客户端在等结果,且处理很快。比如"查询订单详情"——用户在屏幕前等,函数几百毫秒内能返回,就该同步(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 的技术栈和各厂商平台,帮你做选型。
事件驱动不只是"用消息连起来",事件本身的设计有成熟模式可循。模式一,通知事件与携带事件分离:通知事件只带"发生了什么加资源定位"(订单已创建, ID 是多少),消费者按需回查详情;携带事件把完整数据塞进消息体,省一次回查但耦合了 schema——高频与松耦合场景用前者,低频与消费端多样化时后者划算。模式二,版本化与兼容演进:事件 schema 加字段可以、改字段语义不行、删字段要有弃用期——消费者多的体系里,事件契约的管理严格程度不亚于公开 API。模式三,幂等与去重的一等公民地位:至少一次投递是常态,消费者幂等是义务——设计期就定好幂等键(事件 ID 加业务键),把去重逻辑做成基础设施而不是每个消费者各写一套。三个模式的共同指向:事件驱动架构的成熟度不取决于你用了多高级的总线,而取决于事件契约的治理水平——契约乱糟糟的体系,总线再先进也是一堆优雅传输的垃圾。
补最后一个契约治理工具:事件目录——把体系里所有事件(名称、生产者、消费者、schema 版本、负责人)登记成册,变更走目录公告——没有目录的事件体系,半年后就没人说得清"谁在听我的事件";有了目录,删字段前的"广播通知"才有明确的收件人列表。目录可以简陋(一张表),但不能缺席——它和代码里的事件常量一起,构成事件驱动架构的"户籍制度"。
最后补一个调试技巧:事件驱动链路的本地断点调试几乎不可能(触发在云端),务实的替代是"重放开发"——把生产的事件样本(脱敏后)存档,本地与测试环境用重放工具逐条回放;这个习惯既解决调试问题,又顺手建立了回归测试的事件库——一箭双雕的工程实践,值得进团队的模板仓库。
再补一条与成本章的联动:事件驱动不只是架构风格,也是成本结构——事件粒度越细、链路越长,请求数越多账单越碎;设计期的事件粒度(一笔订单一个事件还是每个状态变化一个事件)要同时考虑消费端的灵活性与计费侧的调用量——粒度是弹性与成本的权衡点之一,架构师手里的每个决策都在同时写两份文档:一份代码一份账单。