上周三凌晨两点,支付服务 P99 飙到 8 秒,报警响了 47 条,值班的同事翻了半小时日志才定位到是下游优惠券服务超时。等熔断加上去,已经影响了 1200 笔订单。
说白了,问题就一个:故障发现慢,响应慢,恢复更慢。监控有,报警有,但从”发现问题”到”自动止血”之间,全是人肉。
今天要干的事很简单——把这段人肉流程变成代码。从零搭一个最小的工程闭环:CI 门禁 → Error Budget 消耗 → 自动降级,三个环节串起来。
整体长什么样
1 | 提交代码 |
三个组件,一个不落。下面逐个写代码。
第一步:CI 门禁——发版前先问指标答不答应
CI 门禁的逻辑很直白:上一次发布造成的错误率还没消化完,就别再发了。
用 GitHub Actions 举例。我们在 merge 到 main 之前,跑一个检查:最近 1 小时的错误率是否在安全线内。
1 | # .github/workflows/ci-gate.yml |
关键点:error-budget-check 跑在单元测试之前。budget 不够,后面的步骤根本不会执行。这不是建议,是强制。
如果你的 CI 是 Jenkins,逻辑一样,用 when { beforeAgent true; expression { ... } } 做门禁。
门禁不只是一个检查
其实门禁可以加更多维度,比如:
- 最近 N 次部署的回滚率
- 当前是否有未关闭的 P0/P1 incident
- 代码覆盖率是否低于基线
但第一版别贪多,一个 error budget 检查够了。后面再加。
第二步:Error Budget 计算——把”稳不稳”变成数字
Google SRE Book 里 error budget 的定义很简单:
Error Budget = 1 - SLI 目标值
如果你的 SLI 是”请求成功率 ≥ 99.9%”,那 error budget 就是 0.1%。这 0.1% 是你可以”挥霍”的额度,用完了就得收敛——要么停发版,要么降级。
下面是完整的 error budget 计算服务,Python 实现:
1 | # budget_tracker.py |
运行结果:
1 | 正常: budget=1.0000, burn_rate=0.50 |
燃烧率 102.5 是什么意思?按这个速度,24 小时的 budget 在大约 14 分钟就会烧完。必须立刻止血。
燃烧率那几个数字怎么来的
| 燃烧率 | 含义 | 动作 |
|---|---|---|
| 1x | 刚好用完,正常 | 无 |
| 6x | 4小时耗完24h budget | 阻止发布 |
| 14.4x | 1小时耗完 | 页面告警 + 降级 |
这是 Google SRE Book 推荐的分级策略。6x 和 14.4x 不是魔法数字,是”多快耗完”的倒推。你可以根据自己的 SLA 调整,但分级逻辑不变:慢烧拦发布,快烧要降级。
第三步:自动降级——不用人,代码自己止血
Netflix Hystrix 和阿里 Sentinel 解决的是同一个问题:下游不可用时,别把自己拖死。我们用 Python 写一个轻量的降级框架,逻辑清晰,够用。
1 | # circuit_breaker.py |
跑起来你会看到:
1 | 请求 0: state=closed , result=None # 失败,走降级 |
降级策略不止熔断
熔断只是手段之一。完整的降级策略矩阵:
| 策略 | 适用场景 | 实现 |
|---|---|---|
| 熔断 | 下游超时/错误率高 | Circuit Breaker |
| 限流 | 流量突增 | 令牌桶/滑动窗口 |
| 缓存兜底 | 读多写少 | 返回过期缓存 |
| 功能裁剪 | 非核心功能 | feature flag 关闭 |
生产中通常组合使用。Sentinel 的做法:先限流,限不住再熔断,熔断后走降级。
一个组合示例:
1 | # degradation_manager.py |
DegradationManager 和 ErrorBudgetTracker 配合起来,就形成了完整的自动降级圈:采样 → 算燃烧率 → 切降级级别 → 采样,一直转。
踩坑
这三个坑是我和同事用血泪换来的。
坑1:Error Budget 窗口设太短
一开始我们把窗口设成 1 小时,结果凌晨低峰期(请求量小)随便几个 5xx 就把 budget 烧完了,大清早被电话叫起来。
解法:窗口至少 24 小时,或者用滑动窗口加权——最近的错误权重更高,但不会因为短期波动就触发。
坑2:熔断器超时设太短
熔断等 10 秒就去试探下游,下游还在重启,试探失败又熔断,来回弹。P99 不降反升。
解法:超时至少设成下游平均恢复时间的 2 倍。优惠券服务重启要 30 秒,熔断超时就设 60 秒。另外,半开状态别只放一个请求过去,放 3-5 个,成功率统计才有意义。
坑3:降级了但没人知道
自动降级跑得好好的,但没人关注降级事件。结果降级跑了一整天,非核心功能一直关着,用户投诉才知道。
解法:每次降级级别变更必须发通知——Slack/钉钉/企微都行,别只写日志。通知里带上当前 budget 剩余和燃烧率,让值班的人能快速判断要不要介入。
1 | def _apply_level(self, level: int): |
下一步
最小闭环跑起来之后,可以往这些方向扩展:
- 多服务联动:一个服务降级时,上游自动切换到轻量模式,避免级联失败
- 混沌工程:用 Chaos Monkey 定期注入故障,验证闭环是否真的转得起来
- 指标可视化:Grafana dashboard 展示 budget 消耗曲线和降级事件时间线
- 配置中心化:熔断阈值、budget 策略不要硬编码,放到 Apollo/Nacos 里动态调整
- 故障复盘自动化:每次降级事件自动生成 timeline,减少人工梳理
核心就一件事:让系统在出问题时能自己止血,而不是等人来救。从这三个组件开始,先把圈转起来,再慢慢加。