模块 08第 3 学期 · 可靠性与扩展10 海报条目
异步、事件与实时
登录后可在不同设备间同步进度。在此之前,进度只保存在当前浏览器中。
为什么重要
发送邮件、生成 PDF、调用 LLM agent、处理 webhook:如果这些都在用户请求里同步执行,请求就会变慢,会撞上 serverless 的时长限制而超时,一旦崩溃还会丢失工作。靠谱的系统正是靠异步设计来保持快速、不丢数据。
学习目标
- 把耗时或不可靠的工作移到持久化的后台任务中。
- 在队列、pub/sub 和事件驱动设计之间做出选择。
- 安全地处理 webhook(验签、去重、快速确认、异步处理)。
- 用 saga 和 outbox 模式协调多步骤工作流。
- 选对实时传输方式:WebSockets、SSE 或长轮询。
- 让定时任务即使重叠执行或错过一次也安全。
课程内容
8.1 队列与 worker(15)
模式:请求做校验,把意图写入数据库,任务入队,返回带任务 ID 的 202 Accepted。worker 负责处理、更新状态、发送通知。
- 实际上投递语义是至少一次(at-least-once):每个任务处理函数都要设计成幂等的。
- 可选方案:托管服务(SQS、Cloud Tasks)、基于 Redis(BullMQ)、基于 Postgres(pg-boss、Graphile Worker)、持久化工作流引擎(Inngest、Trigger.dev、Temporal)。
- 持久化工作流引擎往往最适合 AI agent:每一步都会做 checkpoint,并能独立重试。
8.2 Pub/Sub 与事件驱动架构(16, 17)
- 队列: 一条消息,一个消费者(一项要做的任务)。
- Pub/Sub: 一个事件,多个互相独立的订阅者(一件已经发生的事实:
order.paid)。 - 事件驱动: 服务对事件做出反应,而不是彼此直接调用。解耦了,但更难追踪;需要事件 schema 和良好的可观测性。
- 事件用过去时命名;给事件 schema 做版本管理;带上事件 ID 以便去重。
8.3 分布式事务、outbox 与 saga(18, 19)
- 你不可能让数据库、Stripe 和邮件服务商共处同一个 ACID 事务。
- Transactional outbox(事务性发件箱): 在同一个数据库事务里,同时写入业务变更和一条 outbox 记录;由中继程序发布 outbox 记录。这样可以避免“保存了但没发布”或“发布了但没保存”。
- Saga: 一连串本地步骤,每一步都有对应的补偿操作(锁定库存 → 扣款 → 创建发货单;如果发货失败:退款、释放库存)。可以是编排式(有一个协调者),也可以是协同式(通过事件)。
- 两阶段提交确实存在,但在现代 Web 系统中很少适用。
8.4 Webhook(101)
接收方:
- 验证签名(用共享密钥做 HMAC)和时间戳,防止伪造和重放。
- 按服务商的事件 ID 去重(唯一约束)。
- 快速确认(几秒内返回 2xx),然后在后台任务里处理。
- 处理乱序投递;拿不准时向服务商拉取当前状态。 发送方:对 payload 签名,带退避重试,为客户提供投递日志和重放功能,防范客户提供的 URL 带来的 SSRF。
8.5 定时任务(21)
- 假设任何一次运行都可能被跳过、延迟或执行两次。让任务幂等且可续跑。
- 用锁防止重叠执行(数据库 advisory lock)。
- 分批处理并做 checkpoint,而不是一个巨大的事务。
- 用“dead man's switch”心跳来监控(任务没有运行时告警)。
- 调度时间用 UTC 存储;注意其他市场的夏令时。
8.6 实时传输(22, 23, 24)
| 传输方式 | 方向 | 适用场景 |
|---|---|---|
| SSE | 服务端到客户端 | 流式输出 LLM token、通知、进度。简单,基于 HTTP,自动重连 |
| WebSockets | 双向 | 聊天、协作、实时游戏、语音 |
| 长轮询 | 模拟推送 | 流式传输被拦截时的兜底方案 |
| 注意事项:连接要认证,长连接要定期重新授权,处理重连和漏掉的消息(从最后一个事件 ID 续传),serverless 平台可能限制连接时长;规模上来后使用托管实时服务(Supabase Realtime、Pusher、Ably、Cloudflare Durable Objects)。 |
实验 8:改成异步
- 把你最慢的同步操作(LLM 调用、PDF、邮件)移到持久化任务里,通过状态轮询或 SSE 展示进度。
- 实现一个 webhook 接收端(Stripe 或 Resend),包含验签、去重和异步处理。把同一个事件重放 10 次;证明只产生一次效果。
- 实现一个带补偿的 3 步 saga;在第 3 步注入失败;证明能正确回滚。
- 加一个带防重叠锁和心跳告警的定时任务。
完成标准(Definition of Done)
AI 提示词包
- “找出请求处理函数中所有调用外部服务或可能超过 1 秒的操作。建议哪些应该移到后台任务。”
- “实现这个 webhook 处理函数:用恒定时间比较验证签名,拒绝过期的时间戳,用唯一约束按 event id 去重,把处理任务入队,返回 200。”