模块 07第 3 学期 · 可靠性与扩展8 海报条目

弹性设计模式

登录后可在不同设备间同步进度。在此之前,进度只保存在当前浏览器中。

为什么重要

每一个外部调用(数据库、支付网关、LLM 服务商、短信)迟早都会变慢、失败,或者失败到一半。AI 生成的代码几乎总是假设一切顺利。只要有一个慢依赖没设超时,就可能耗尽线程、连接或 serverless 并发,拖垮整个应用。

学习目标

  1. 给每一个出站调用设置超时,并根据延迟预算来定数值。
  2. 安全地重试:只重试幂等操作,使用带 jitter(随机抖动)的指数退避,并设上限。
  3. 用熔断器和降级方案实现优雅降级。
  4. 应用 backpressure(背压)和死信队列,防止过载和毒消息引发连锁故障。
  5. 暴露真正有意义的健康检查。

课程内容

7.1 超时(11)

  • 很多 HTTP 客户端的默认超时是无限长,或者长达几分钟。要显式设置:连接超时加请求超时。
  • 从预算倒推:如果页面必须在 2 s 内响应,并且要依次调用三个服务,那每个服务分到的时间都要远低于 2 s。
  • 传递截止时间:如果调用方只剩 300 ms,就不要再发起一个 2 s 的调用。
  • LLM 调用:使用流式输出,同时设置首 token 超时和总超时。

7.2 重试与指数退避(12, 13)

  • 只重试暂时性错误(超时、429、502、503、504、连接被重置)。绝不重试 400/401/403/422。
  • 只重试幂等操作,或带有 idempotency key 的操作(04 · API 与契约)。
  • 退避:delay = min(cap, base * 2^attempt),再加上随机 jitter,避免成千上万个客户端同时重试(惊群效应,thundering herd)。
  • 限制重试次数(通常为 3 次)和总时长。遵守 Retry-After。
  • 只在一层做重试。每层都重试会成倍放大:3 x 3 x 3 = 每次用户操作 27 次调用。

7.3 熔断器(10)

状态:closed(关闭,正常)→ 失败达到阈值后 open(打开,快速失败,不再调用)→ 冷却后 half-open(半开,放少量请求试探)→ 恢复健康则回到 closed。 搭配降级方案:缓存数据、功能降级、排队并提示“稍后通知您”,或者切换到备用服务商(例如第二家 LLM 厂商)。

7.4 隔舱与背压(44)

  • 隔舱(Bulkheads): 每个依赖使用独立的资源池和限额,这样慢吞吞的 LLM 不会占用登录接口需要的容量。
  • 背压(Backpressure): 当消费者跟不上时,要往回推(有界队列、返回带 Retry-After 的 429/503、丢弃低优先级任务),而不是一直缓冲到内存耗尽。

7.5 死信队列(20)

处理失败 N 次后,把消息移进 DLQ,而不是无休止地重试(一条“毒消息”可能堵死整个队列)。对 DLQ 积压量设置告警。做一个能查看、修复和重放消息的工具。

7.6 健康检查(56, 57)

  • Liveness(存活): “进程还活着吗?”开销要低;绝不检查依赖(否则数据库抖一下,所有 pod 都会被重启)。
  • Readiness(就绪): “现在能接流量吗?”检查关键依赖;为 false 时把实例从负载均衡中摘除。
  • Startup(启动): 用于启动缓慢的服务。
  • 一个公开的状态页,数据来自对真实用户路径的合成监测。

7.7 优雅降级

按功能逐一决定:这个依赖挂了,用户会看到什么?把它写下来。例如:隐藏推荐内容;支付进入队列并给出清晰提示;AI 助手显示“暂时不可用”,而不是一直转圈。

实验 7:在你的笔记本上制造混乱

在应用和它的依赖之间使用 Toxiproxy(或故障注入中间件)。

  1. 给 LLM 服务商加 5 s 延迟,观察结果。加上超时、带 jitter 的重试、熔断器和降级方案后,再观察一次。
  2. 让支付服务商对 50% 的调用返回 503。证明不会重复扣款。
  3. 往队列里注入一条毒消息;证明它进入了 DLQ 并触发告警。
  4. 让数据库宕机 30 s。Readiness 失败,liveness 保持正常,服务无需重启即可恢复。

完成标准(Definition of Done)

AI 提示词包

  • “找出这个仓库里所有的出站网络调用。报告哪些没有超时、哪些在重试非幂等操作、哪些没有降级方案。”
  • “创建一个共享的 HTTP 客户端:带超时,只对暂时性错误用指数退避加 full jitter 重试,并为每个 host 配一个熔断器。”