Administrator
发布于 2024-07-17 / 5486 阅读
88

高可用架构中的降级与兜底设计

大促前一晚压测,一个重要下游依赖突然超时,我们的服务跟着雪崩,错误率冲到 40%。复盘时发现:我们根本没有像样的降级,依赖挂了就硬等、硬等就堆积、堆积就拖死。这次事故后,我把降级与兜底当成架构的一等公民来设计。

降级层次:从浅到深

降级不是"有/无"两个状态,而是分层的连续体。我按影响面从轻到重设计了四层:

  1. 功能降级:次要功能关掉,核心保留。比如商品页关掉"猜你喜欢"推荐,但价格、库存照常;
  2. 质量降级:返回不那么新但可用的数据。推荐从"个性化实时算"退化为"热门榜单缓存";
  3. 兜底数据:依赖全挂,返回静态默认。比如配置中心不可用时,用本地打包的默认配置启动;
  4. 快速失败:连兜底都拿不到,直接拒掉部分请求(熔断),保住剩余容量,而不是全量拖死。

兜底数据:不能临时现想

兜底最怕"平时没准备,出事现编"。我们给每个外部依赖都预设了兜底:

// 价格服务不可用时,用本地缓存的最近一次快照兜底
Price p = priceClient.get(skuId);
if (p == null) {
    p = localCache.getLastKnown(skuId); // 兜底快照,标脏让用户感知
    p = p.withStaleFlag(true);
}

兜底数据要带过期标识,前端展示"价格可能延迟"提示,既保住可用性又对用户诚实。我们甚至把兜底快照的"新鲜度"做成监控指标,一旦大量命中兜底,说明上游真出问题了。

开关配置:降级要秒级可控

降级必须能不发版就触发。我们所有降级开关走配置中心(Nacos),每个开关三态:

状态行为
auto按熔断器自动降级
force_on强制开启该功能(压测用)
force_off强制降级(上游已确认故障,提前关)

大促期间,我们提前把几个已知脆弱依赖设为 force_off,宁可少功能也要保主链路。事后复盘,正是这个提前操作,让核心下单在那天稳住了。

演练机制:降级不是写了就灵

最大的教训是:降级逻辑从没被测过,真出事时它自己也是 bug。我们建立了两类演练:

  • 混沌演练:用 Chaos Mesh 随机 kill 下游 Pod,验证降级是否真的生效,而不是"代码里有但没触发";
  • 红蓝对抗:蓝队故意制造依赖故障,红队靠开关和兜底把服务稳住,演练后必出改进项。

第一次演练就暴露问题:兜底快照的本地缓存因为序列化版本变了,反序列化失败直接抛异常,兜底反而成了新故障点。修掉后,我们给兜底逻辑本身也加了"兜底的兜底"——序列化失败就返回写死的极简默认值。

降级与 SLO 的对齐

降级不是孤立动作,得和 SLO 对齐。我们给核心链路定了 SLO:下单成功率 99.95%、P99 < 200ms。降级决策就围绕这两个数字:成功率跌破 99.9% 自动触发对应依赖的 force_off,P99 超 300ms 持续则熔断最慢的下游。这样降级从"凭经验拍脑袋"变成"看指标决策"。我们还把"降级中"作为一等状态写进监控大盘,值班同学一眼看出系统此刻是完整态还是降级态,避免"以为正常其实在兜底"的错觉。一次大促,推荐服务被主动 force_off,大盘显示降级黄灯,业务方反而安心——知道这是预案而非事故。降级透明化,比藏着掖着更利于协作。

另外降级也要有"恢复"动作。很多团队只设计了降级触发,忘了恢复——依赖恢复后人工忘了开回去,系统长期跑在残血状态。我们给每个 force_off 开关加了"自动探测恢复":依赖健康检查连续通过 5 分钟,提示可恢复,由值班确认或由 auto 态自动回切。降级和恢复成对存在,才是完整的弹性设计。

先到这

《高可用架构中的降级与兜底设计》这块我前前后后踩了不止一次。今天先写这些,后面想到新的再补。

参考