3. 降级策略(Degradation)设计
降级是系统在承受超出设计容量或依赖故障时,主动牺牲非核心功能、保证核心链路可用的关键韧性手段。与熔断不同,降级是主动、有计划的行为,通常由人工或规则触发,而非被动响应错误率。本节聚焦触发条件、Fallback 返回值、缓存降级与静态页面降级四大核心设计点。
3.1 降级触发条件
降级触发条件必须可量化、可监控、可自动化。常见触发维度如下:
| 触发维度 | 典型阈值 | 说明 |
|---|---|---|
| 响应时间(RT) | P99 > 500ms 持续 10s | 接口或依赖响应变慢,触发降级以保护下游 |
| 错误率 | 5xx 比例 > 5% 持续 30s | 依赖服务返回错误比例过高 |
| 并发数 | 活跃线程数 > 80% 线程池 | 系统资源接近瓶颈,主动降级非核心功能 |
| 资源水位 | CPU > 85% 或 内存 > 80% | 基础设施压力过大,降级以释放资源 |
| 人工标记 | 运维/架构师手动开关 | 已知故障或大促预案,提前降级 |
设计建议:降级触发条件应设置多级阈值(如:警告级、降级级、紧急级),避免单点抖动导致频繁降级。同时,降级动作必须记录审计日志,便于事后复盘。
3.2 降级后的默认返回值(Fallback)
Fallback 是降级后向调用方返回的替代结果。设计原则是:保证业务语义完整,避免调用方空指针或逻辑异常。常见 Fallback 策略如下:
- 静态默认值:返回固定值,如空列表
[]、空字符串""、默认数字0、布尔false。 - 缓存快照:返回最近一次成功获取的缓存数据(即使已过期),适用于商品详情、用户信息等读多写少场景。
- 降级对象(Null Object):返回一个实现了正常接口的空操作对象,避免调用方判空。
- 异步降级:返回一个 Future 或 CompletableFuture,内部直接完成默认值,不阻塞调用方。
// Java 示例:使用 Sentinel 定义 Fallback
@SentinelResource(
value = "getUserInfo",
fallback = "getUserInfoFallback",
fallbackClass = UserServiceFallback.class
)
public UserInfo getUserInfo(Long userId) {
// 正常业务逻辑
return userClient.query(userId);
}
// Fallback 方法:返回缓存快照或默认用户
public UserInfo getUserInfoFallback(Long userId, Throwable t) {
// 1. 尝试从本地缓存获取
UserInfo cached = localCache.get(userId);
if (cached != null) {
return cached;
}
// 2. 返回默认匿名用户
return new UserInfo(userId, "anonymous", "default@unknown.com");
}
避坑:Fallback 方法本身必须轻量、无外部依赖,否则降级后可能引发二次故障。禁止在 Fallback 中再次调用 RPC、数据库或复杂计算。
3.3 缓存降级
缓存降级是当缓存层(Redis、Memcached、本地缓存)不可用或响应超时时,系统自动切换至备用数据源或直接返回降级结果。设计要点:
- 多级缓存降级:Redis → 本地进程缓存 → 数据库 → 默认值,逐级降级。
- 缓存穿透保护:当缓存 miss 且数据库也异常时,直接返回空结果并短暂缓存空值(布隆过滤器辅助)。
- 缓存雪崩预防:缓存过期时间加随机偏移,避免大量 key 同时失效触发降级。
// 多级缓存降级伪代码
public Product getProduct(Long productId) {
// 第一级:Redis
Product p = redisCache.get(productId);
if (p != null) return p;
// 第二级:本地缓存(Caffeine)
p = localCache.get(productId);
if (p != null) return p;
// 第三级:数据库查询
try {
p = db.query(productId);
if (p != null) {
redisCache.set(productId, p, 300 + random.nextInt(60));
localCache.put(productId, p);
return p;
}
} catch (Exception e) {
// 数据库异常,降级返回默认商品
logger.warn("DB query failed, degrade for product: {}", productId);
return Product.DEFAULT;
}
// 第四级:无数据,返回默认值
return Product.DEFAULT;
}
3.4 静态页面降级
静态页面降级适用于 Web 前端或 API 网关层。当后端服务不可用时,直接返回预先生成的静态 HTML、JSON 或 CDN 资源,保证用户可访问基本页面。
| 降级场景 | 静态资源类型 | 示例 |
|---|---|---|
| 首页/活动页 | 静态 HTML | 返回 CDN 上的活动静态页,无动态数据 |
| 商品详情 | 静态 JSON | 返回预先生成的商品快照 JSON(T+1 更新) |
| 用户中心 | 静态错误页 | 返回“系统繁忙,请稍后再试”的友好页面 |
| 搜索/推荐 | 静态结果集 | 返回热门搜索词或默认推荐列表 |
最佳实践:静态页面降级应配合网关层(如 Nginx、Kong、Spring Cloud Gateway)实现。网关根据后端健康状态自动切换路由,将请求导向静态资源服务器或 CDN,避免请求穿透到故障后端。
3.5 降级策略决策流程
下图展示了从触发条件判断到最终 Fallback 返回的完整决策链路:
3.6 降级策略对比与选型
| 策略 | 适用场景 | 数据新鲜度 | 实现复杂度 | 资源消耗 |
|---|---|---|---|---|
| 静态默认值 | 非关键字段、展示类信息 | 无 | 低 | 极低 |
| 缓存快照 | 商品详情、用户信息、配置 | 分钟级~小时级 | 中 | 低 |
| 静态页面 | 首页、活动页、错误页 | 天级 | 中高 | 低(CDN) |
| 异步降级 | 非实时性要求高的写入操作 | 秒级~分钟级 | 高 | 中 |
选型建议:优先使用缓存快照 + 静态默认值组合,覆盖 80% 降级场景。对于面向用户的 Web 页面,务必配置静态页面降级,避免后端故障导致白屏或 502。