ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

第39章:FastAPI应用SRE 落地——发布、回滚、降级与故障演练

第39章:FastAPI应用SRE 落地——发布、回滚、降级与故障演练 1. 项目背景业务场景“订单中台”第 30 章已上线 6 个月日均 50 万订单。但最近一个月发生了 3 次事故事故一一次常规发布上线后支付回调接口 P99 从 200ms 飙升至 8000ms持续 15 分钟才发现——因为发布过程中没有自动回滚、没有监控告警。运维手动回滚了 5 分钟才恢复。影响用户 2000。事故二Redis 集群宕机——缓存全部穿透到数据库数据库连接池耗尽。整个订单服务不可用 40 分钟。复盘发现没有任何降级机制——即使 Redis 挂了接口也应该能用无缓存模式继续服务。事故三第三方支付网关超时 30 秒——但订单服务的熔断器配置错误超时被无限放大线程池耗尽。更糟的是故障演练从来没做过——支付网关超时这个场景从来没有模拟测试过。老板问我们服务的可用性到底是多少99.9% 还是 99%运维答不上来——因为没有定义 SLO。痛点没有 SRE 实践的线上服务 定时炸弹没有 SLO 就没有目标团队不知道系统应该多可靠——99.9% 和 99% 的年故障时间相差 8 小时对应截然不同的架构投入。发布像赌博每次发布都没有预案——出了问题再说→ 出了问题就手忙脚乱。没有降级开关依赖的每个服务都假设永远可用——一个服务挂了全链路崩溃。故障演练从不做第一次面对Redis 挂了这种场景就是线上真的挂了——不知道怎么恢复。事故复盘无改进事故后写个复盘报告然后就没有然后了——改进项没有跟踪到落地。2. 项目设计场景第 3 次事故复盘会。老板拍桌子了——“这季度第三次了你们 SRE 呢”小胖小声“SRE 是啥Site Reliability Engineering不就是要运维做开发的事吗”大师“不对。SRE 是一种用软件工程方法解决运维问题的实践。核心工具就是三样——SLO、Error Budget、Fault Injection。不是让运维写代码而是让开发对’可靠性’负责。”小白“SLO 具体怎么定99.9% 还是 99.99%——差一个 9 就是 10 倍的投入。”大师在白板上写SLO每月允许故障时间年故障时间投入成本99%7.2 小时3.65 天低99.9%43 分钟8.76 小时中99.95%21 分钟4.38 小时中高99.99%4.3 分钟52 分钟极高需多活架构“对于订单中台99.95% 是一个合理的目标——每月最多允许 21 分钟不可用。这个数字决定了你的发布策略、回滚速度、多副本数量。”技术映射Error Budget (1 - SLO) × 时间段。如果 SLO 99.95%月 Error Budget 21 分钟。如果一次事故消耗了 15 分钟这个月还剩 6 分钟的容错额度。当 Error Budget 耗尽时团队必须暂停所有新功能发布——只做可靠性改进——直到下个周期重置。小胖“降级策略具体怎么实现Redis 挂了不能直接让接口 500 吧”大师“降级有四种经典策略——从轻到重”降级策略触发条件效果缓存兜底Redis 不可用时 → 直接查数据库P95 可能升到 200ms但服务不中断只读模式数据库主库故障 → 只读从库用户可以浏览但无法下单功能开关推荐系统故障 → 关闭猜你喜欢核心下单不受影响异步排队支付网关超时 → 订单排队后台异步确认用户先收到订单处理中通知“关键在于——降级是提前设计好的不是出故障时临时想的。每一级降级都需要代码支持——if redis_unavailable: return db_query()。”小白“故障演练怎么设计不能真把生产 Redis 停了吧”大师故障演练分三级桌面推演白板上走一遍故障流程——哪个步骤由谁执行、用什么工具、预计恢复时间灰度演练只对 1% 的请求注入故障如 Redis 延迟增加 500ms——观察系统是否触发告警、是否自动降级全链路演练在预发环境真假混合停止整个 Redis 集群——验证降级逻辑和恢复流程3. 项目实战——订单中台的 SRE 体系构建分步实现步骤一定义 SLO 和 SLI目标量化的可靠性目标config/slo.yamlslos:-name:订单创建成功率sli:rate(orders_created_total{statussuccess}[5m]) / rate(orders_created_total[5m])target:99.95# %window:28d# 月窗口-name:订单创建 P99 延迟sli:histogram_quantile(0.99, order_creation_duration_seconds_bucket[5m])target:0.5# 秒window:28d-name:支付回调成功率sli:rate(payment_callback_total{statussuccess}[5m]) / rate(payment_callback_total[5m])target:99.9# %window:28derror_budget_policy:burn_rate_threshold:10# 消耗速率阈值10x 意味 2 小时内耗尽月预算freeze_on_exhausted:true# Error Budget 耗尽后冻结发布步骤二实现降级策略目标依赖故障时优雅退化app/core/degradation.pyimportasynciofromenumimportEnumfromfunctoolsimportwrapsfromapp.infrastructure.cacheimportcacheclassDegradationLevel(Enum):FULLfull# 无降级NO_CACHEno_cache# 不使用缓存READ_ONLYread_only# 只读MINIMALminimal# 最小化服务classDegradationManager:降级管理器——通过 Redis 存储全局降级状态多实例共享_instanceNoneDEFAULT_LEVELDegradationLevel.FULLdef__init__(self):self._level:DegradationLevelself.DEFAULT_LEVELasyncdefget_level(self)-DegradationLevel:从 Redis 读取当前降级级别多实例一致try:rawawaitcache.get(degradation:level)returnDegradationLevel(raw)ifrawelseself.DEFAULT_LEVELexceptException:returnself._level# Redis 不可用时用本地缓存asyncdefset_level(self,level:DegradationLevel):设置降级级别管理员操作awaitcache.set(degradation:level,level.value)self._levelleveldefdegraded(self,level:DegradationLevel):装饰器——当前降级级别高于指定级别时跳过执行defdecorator(func):wraps(func)asyncdefwrapper(*args,**kwargs):currentawaitself.get_level()ifcurrent.valuelevel.value:returnNone# 降级不执行returnawaitfunc(*args,**kwargs)returnwrapperreturndecorator degradationDegradationManager()# ─── 使用示例 ───classOrderService:asyncdefget_recommendations(self,user_id:int):推荐商品——READ_ONLY 降级时跳过if(awaitdegradation.get_level()).valueDegradationLevel.READ_ONLY.value:return[]# 降级不推荐returnawaitrecommend_client.get(user_id)步骤三实现发布与回滚自动化目标金丝雀发布 自动回滚scripts/deploy/canary_release.sh#!/bin/bash# 金丝雀发布脚本NEW_VERSION$1STABLE_REPLICAS9CANARY_REPLICAS1# 1. 部署 1 个金丝雀 Pod新版本kubectl scale deployment order-api-canary--replicas$CANARY_REPLICASkubectlsetimage deployment/order-api-canaryapiregistry.example.com/order-api:$NEW_VERSION# 2. 等待金丝雀 Pod Readykubectlwait--forconditionready pod-lversioncanary--timeout60s# 3. 监控金丝雀的错误率 3 分钟sleep180ERROR_RATE$(curl-shttp://prometheus:9090/api/v1/query?queryrate\(http_requests_total{version\canary\,status~\5..\}[3m]\)|jq.data.result[0].value[1])STABLE_ERROR_RATE$(curl-shttp://prometheus:9090/api/v1/query?queryrate\(http_requests_total{version\stable\,status~\5..\}[3m]\)|jq.data.result[0].value[1])if(($(echo $ERROR_RATE$STABLE_ERROR_RATE*2|bc-l)));thenecho⚠️ 金丝雀错误率超稳定版 2 倍——自动回滚kubectl scale deployment order-api-canary--replicas0exit1fi# 4. 金丝雀通过——滚动更新全部 Podecho✅ 金丝雀验证通过——推进全量发布kubectlsetimage deployment/order-apiapiregistry.example.com/order-api:$NEW_VERSIONkubectl rollout status deployment/order-api步骤四故障演练脚本——模拟 Redis 宕机scripts/chaos/simulate_redis_failure.sh#!/bin/bash# 混沌工程——模拟 Redis 不可用在灰度环境执行echo 故障演练Redis 宕机 echo开始时间:$(date)# 1. 通过 iptables 丢弃 Redis 端口的流量模拟网络故障kubectlexec-itredis-0 -- iptables-AINPUT-ptcp--dport6379-jDROPechoRedis 端口已封锁——观察系统行为# 2. 检查系统是否触发降级sleep10DEGRADATION_LEVEL$(curl-shttp://order-api/api/v1/admin/degradation/level)echo当前降级级别:$DEGRADATION_LEVEL# 3. 检查自动告警是否触发# (Grafana → Prometheus Alert)# 4. 等待 5 分钟观察sleep300# 5. 恢复 Redisecho恢复 Redis 连接kubectlexec-itredis-0 -- iptables-DINPUT-ptcp--dport6379-jDROPecho故障演练结束时间:$(date)echo 复盘检查清单 echo☐ 订单服务是否自动降级到无缓存模式echo☐ 用户下单是否仍然可用echo☐ 告警是否触发echo☐ 恢复后是否自动恢复正常模式步骤五事故复盘模板docs/postmortem_template.md# 事故复盘{标题} | 项目 | 详情 | |------|------| | 事故时间 | YYYY-MM-DD HH:MM - HH:MM | | 影响时长 | X 分钟 | | 影响范围 | X 用户 / X 订单 | | 严重级别 | P0/P1/P2 | | 事故处理人 | xxx | ## 时间线 - 14:05 - 发布 v1.2.0 - 14:08 - 监控告警5xx 错误率 1% - 14:10 - xxx 发现告警开始排查 - 14:15 - 定位根因新版本中 DB 连接池参数配置错误 - 14:17 - 执行回滚到 v1.1.0 - 14:20 - 服务恢复正常 ## 根因分析 {5 Whys 或 Fishbone} ## 改进项 | 项目 | 负责人 | 截止日期 | 状态 | |------|--------|---------|------| | 增加 DB 连接池参数的单元测试 | xxx | YYYY-MM-DD | TODO | | CI 增加启动时连接池健康检查 | yyy | YYYY-MM-DD | TODO | | 金丝雀发布的时间窗口从 3 分钟延长到 10 分钟 | zzz | YYYY-MM-DD | IN PROGRESS | ## Error Budget 影响 本次事故消耗 12 分钟 / 本月剩余 9 分钟完整代码清单本章代码见column/code/chapter39/主要文件config/slo.yamlSLO 和 Error Budget 定义app/core/degradation.py降级管理器scripts/deploy/canary_release.sh金丝雀发布脚本scripts/chaos/simulate_redis_failure.sh故障演练脚本docs/postmortem_template.md事故复盘模板测试验证# 验证降级开关curl-XPOST http://localhost:8000/api/v1/admin/degradation/set?levelread_onlycurlhttp://localhost:8000/api/v1/admin/degradation/level# → {level: read_only}# 降级后验证推荐接口返回空curlhttp://localhost:8000/api/v1/recommendations?user_id1# → {code: 0, data: []} ← 降级生效# 恢复curl-XPOST http://localhost:8000/api/v1/admin/degradation/set?levelfull4. 项目总结优点 缺点对比SRE 方案自建本章Gremlin混沌工程 SaaSPagerDuty DataDog无 SRESLO 定义手动 PromQL内置模板内置无降级自建开关 代码内判断无无无故障演练脚本拖拽式无无成本零人力投入$10k/年$5k/年零但事故成本高适用场景✓ SRE 实践适用于有 SLA 承诺的商业 API需证明可用性日均流量 10 万的核心业务已有专职运维/开发团队的项目需要等保三级认证的企业✗ 不适用内部工具、原型项目故障影响范围小3 人以下团队先把业务做稳注意事项SLO 是业务指标不是技术指标CPU 80%不是 SLO——CPU 80% 导致支付成功率下降才是。定义 SLO 时要问用户感受到的问题是什么降级不是不返回错误降级意味着用降低的服务质量继续服务——不是服务挂了返回空。两者的用户体验天差地别。Error Budget 用完了要真的暂停发布如果 Error Budget 是纸面上的但没有人遵守SLO 就毫无意义。常见踩坑经验案例一降级开关被误操作导致全站 read-only现象凌晨 2 点运维排查问题时临时设了read_only模式——排查完忘了恢复。第二天早上用户发现不能下单。根因降级开关没有时间限制——设了就一直生效。解决为降级开关设TTL如 5 分钟超时自动恢复。或者需要至少 2 人确认才能设置高级别降级。案例二故障演练把生产数据库打挂了现象全链路演练中iptables -j DROP封锁了主库端口——主从切换失败从库数据延迟 5 分钟未同步用户看到旧数据。根因数据库的主从复制延迟在演练前没有被检查。解决故障演练前必须检查所有故障恢复路径——主从复制状态、从库数据延迟、应用重新连接能力。案例三告警风暴——凌晨 3 点收到 50 条告警现象Redis 挂了触发了 50 条告警——“Redis 不可达”、“缓存命中率 0%”、“订单创建失败率上升”……运维被 call 醒后发现只有 1 条是根因其他 49 条是症状。根因告警之间没有关联——每个告警独立触发但实际上是同一个根因的级联效应。解决告警根因收敛——只告警订单 SaaS 不可用顶层指标而非底层每个组件。思考题初级为订单中台实现一个手动降级Admin API——支持通过 API 设置 4 种降级级别降级级别存储在 Redis 中多实例共享。降级时自动关闭非核心功能推荐、通知、导出。进阶设计一个自动降级系统——当支付网关 P99 5s 且持续 2 分钟时自动切换到异步排队模式订单先创建、支付异步确认。系统恢复后自动切回正常模式。提示需要 Prometheus 指标查询 决策模块 降级执行器。答案提示第 1 题参考本章的DegradationManager。第 2 题用一个后台协程定期查询 Prometheus API 获取 P99 值满足条件时调用degradation.set_level(ASYNC_QUEUE)。恢复条件P99 1s 持续 5 分钟则恢复到 FULL。第 40 章综合实战会融合自动降级。延伸阅读与资源Java 工程师进阶从 JVM 生产排障到OpenJDK原理NumPy 从入门到生产落地全链路实战指南科学计算/向量化Redis 8 实战精讲从 CRUD 到源码构建高可用缓存系统Redis 实战修炼与原理进阶Python 3实战精进从脚本到高并发订单引擎python入门Rquests从菜鸟脚本到企业级SDK的网络实战圣经Milvus向量数据库实战修炼从 0 到 1精通向量检索与生产落地MongoDB 实战进阶与内核修炼后端工程师的 AI 转型第一课Ollama 与私有化大模型实战10倍开发者的 Dify 魔法书从零构建全栈 AI 应用后端工程师转型AI第一课-Ollama 与私有化大模型实战大型语言模型(LLM) vLLM 高性能推理落地实战Agent开发之LlamaIndex 实战修炼与源码进阶大语言模型Transformers 实战修炼与源码剖析
返回列表