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 返回的完整决策链路:

降级策略决策流程 降级策略决策流程 请求到达 触发条件判断 RT / 错误率 / 并发 / 资源 / 人工 触发 未触发 降级策略选择 Fallback / 缓存降级 / 静态页面 正常业务处理 调用依赖服务 / 查询数据库 执行降级 返回默认值 / 缓存快照 / 静态页 正常返回结果 实时数据 / 动态页面 返回调用方 降级策略需配合熔断器状态机,避免频繁切换;降级后应定期探测恢复状态

3.6 降级策略对比与选型

策略 适用场景 数据新鲜度 实现复杂度 资源消耗
静态默认值 非关键字段、展示类信息 无 低 极低
缓存快照 商品详情、用户信息、配置 分钟级~小时级 中 低
静态页面 首页、活动页、错误页 天级 中高 低(CDN)
异步降级 非实时性要求高的写入操作 秒级~分钟级 高 中
选型建议:优先使用缓存快照 + 静态默认值组合,覆盖 80% 降级场景。对于面向用户的 Web 页面,务必配置静态页面降级,避免后端故障导致白屏或 502。