降级策略:返回默认值/空结果/缓存

降级的核心是返回一个「有损但有用」的结果。

降级不是简单地返回 null 或空数组,那样会导致下游代码空指针异常或逻辑错误。好的降级策略应该返回一个「有意义」的默认值,让调用方能够继续处理,而不是崩溃。

三种降级策略

策略适用场景数据新鲜度实现复杂度
默认值所有场景无数据简单
缓存数据读多写少可能过时中等
静态内容内容展示类完全过时简单

默认值降级

基础默认值

DefaultValueFallback.java
@Service
public class DefaultValueFallback {

    // 用户服务降级
    public User getDefaultUser() {
        return User.builder()
            .id(0L)
            .name("Guest")
            .email("guest@example.com")
            .level(UserLevel.NORMAL)
            .build();
    }

    // 商品服务降级
    public Product getDefaultProduct() {
        return Product.builder()
            .id(0L)
            .name("商品信息加载中")
            .status(ProductStatus.UNAVAILABLE)
            .price(BigDecimal.ZERO)
            .build();
    }

    // 列表服务降级
    public List<Product> getDefaultProductList() {
        // 返回空列表,而不是 null
        return Collections.emptyList();
    }

    // 数字服务降级
    public Integer getDefaultCount() {
        return 0;
    }

    // 布尔服务降级
    public Boolean getDefaultBoolean() {
        return false;
    }
}

业务语义默认值

默认值应该有业务意义,不能随便返回一个空对象:

BusinessDefaultFallback.java
@Service
public class BusinessDefaultFallback {

    // 库存服务降级:假设库存充足
    public Inventory getDefaultInventory(Long productId) {
        return Inventory.builder()
            .productId(productId)
            .available(999)  // 返回大数字,让用户能下单
            .reserved(0)
            .build();
    }

    // 价格服务降级:返回原价
    public Price getDefaultPrice(Long productId) {
        return Price.builder()
            .productId(productId)
            .originalPrice(BigDecimal.ZERO)  // 价格为零可能有问题
            .discountPrice(BigDecimal.ZERO)
            .build();
    }

    // 会员折扣降级:不打折
    public Discount getDefaultDiscount(Long userId) {
        return Discount.builder()
            .userId(userId)
            .discountRate(BigDecimal.ONE)  // 1 表示不打折
            .description("系统繁忙,暂无折扣")
            .build();
    }

    // 物流服务降级:假设普通配送
    public ShippingInfo getDefaultShippingInfo(Long orderId) {
        return ShippingInfo.builder()
            .orderId(orderId)
            .shippingMethod("standard")
            .estimatedDays(7)
            .cost(BigDecimal.ZERO)
            .build();
    }
}

缓存降级

本地缓存降级

LocalCacheFallback.java
@Service
public class LocalCacheFallback {

    private final LoadingCache<String, Object> localCache;

    public LocalCacheFallback() {
        this.localCache = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build(key -> loadFromRemote(key));
    }

    public Product getProduct(Long productId) {
        String cacheKey = "product:" + productId;

        try {
            // 尝试获取实时数据
            Product product = productService.getProduct(productId);
            // 成功时更新缓存
            localCache.put(cacheKey, product);
            return product;
        } catch (Exception e) {
            // 降级:返回缓存数据
            Product cached = (Product) localCache.getIfPresent(cacheKey);
            if (cached != null) {
                log.warn("返回缓存商品数据: productId={}", productId);
                return cached;
            }

            // 缓存也没有,返回默认值
            log.error("商品服务降级,无缓存: productId={}", productId);
            return getDefaultProduct(productId);
        }
    }

    private Product loadFromRemote(String cacheKey) {
        // 初始化时加载的数据
        throw new UnsupportedOperationException("Should not be called directly");
    }
}

Redis 缓存降级

RedisCacheFallback.java
@Service
public class RedisCacheFallback {

    private final RedisTemplate<String, Object> redisTemplate;
    private final ProductService productService;

    private static final String PRODUCT_CACHE_KEY = "product:";
    private static final Duration CACHE_TTL = Duration.ofHours(1);

    public Product getProduct(Long productId) {
        String cacheKey = PRODUCT_CACHE_KEY + productId;

        try {
            // 尝试获取实时数据
            Product product = productService.getProduct(productId);
            // 更新缓存
            redisTemplate.opsForValue().set(cacheKey, product, CACHE_TTL);
            return product;
        } catch (Exception e) {
            // 降级:返回缓存数据
            return getCachedProduct(productId);
        }
    }

    private Product getCachedProduct(Long productId) {
        String cacheKey = PRODUCT_CACHE_KEY + productId;
        Object cached = redisTemplate.opsForValue().get(cacheKey);

        if (cached instanceof Product) {
            log.warn("返回 Redis 缓存商品: productId={}", productId);
            return (Product) cached;
        }

        log.error("商品服务降级,无缓存: productId={}", productId);
        return getDefaultProduct(productId);
    }
}

缓存降级策略

CacheFallbackStrategy.java
@Service
public class CacheFallbackStrategy {

    public <T> T getWithFallback(String cacheKey,
                                  Supplier<T> remoteLoader,
                                  Class<T> type) {
        // 1. 尝试从缓存获取
        T cached = getFromCache(cacheKey, type);
        if (cached != null) {
            return cached;
        }

        // 2. 尝试从远程获取
        try {
            T result = remoteLoader.get();
            if (result != null) {
                updateCache(cacheKey, result);
            }
            return result;
        } catch (Exception e) {
            // 3. 降级:返回缓存或默认值
            return getFallback(cacheKey, type, e);
        }
    }

    private <T> T getFallback(String cacheKey, Class<T> type, Exception e) {
        log.warn("服务降级: cacheKey={}, error={}", cacheKey, e.getMessage());

        // 尝试返回旧缓存
        T cached = getFromCache(cacheKey, type);
        if (cached != null) {
            return cached;
        }

        // 返回类型对应的默认值
        return getDefaultValue(type);
    }

    @SuppressWarnings("unchecked")
    private <T> T getDefaultValue(Class<T> type) {
        if (type == List.class) {
            return (T) Collections.emptyList();
        }
        if (type == Map.class) {
            return (T) Collections.emptyMap();
        }
        if (type == String.class) {
            return (T) "";
        }
        if (type == Integer.class || type == int.class) {
            return (T) Integer.valueOf(0);
        }
        if (type == Boolean.class || type == boolean.class) {
            return (T) Boolean.FALSE;
        }
        return null; // 对于复杂类型,返回 null 需要调用方处理
    }
}

空结果 vs 默认值

空结果和默认值的选择:

场景推荐策略原因
列表数据空列表 []调用方可以遍历,不会 NPE
单个对象默认对象调用方可以访问属性
数字统计默认 0下游计算不会出错
布尔判断根据业务语义决定需要判断「未查到」还是「查到 false」
EmptyVsDefault.java
public class EmptyVsDefault {

    // 用户列表:返回空列表
    public List<User> getUsers(Long organizationId) {
        try {
            return userService.getUsersByOrganization(organizationId);
        } catch (Exception e) {
            // 降级:返回空列表
            return Collections.emptyList();
        }
    }

    // 用户详情:返回默认用户
    public User getUser(Long userId) {
        try {
            return userService.getUser(userId);
        } catch (Exception e) {
            // 降级:返回默认用户
            return User.guest();
        }
    }

    // 订单数量:返回 0
    public Integer getOrderCount(Long userId) {
        try {
            return orderService.getOrderCount(userId);
        } catch (Exception e) {
            // 降级:返回 0
            return 0;
        }
    }

    // 会员状态:需要区分「未查到」和「不是会员」
    public Optional<Membership> getMembership(Long userId) {
        try {
            return Optional.ofNullable(membershipService.getMembership(userId));
        } catch (Exception e) {
            // 降级:返回空 Optional,表示未知
            return Optional.empty();
        }
    }
}

降级组合策略

CompositeFallback.java
public class CompositeFallback {

    public RecommendationResult getRecommendation(Long userId) {
        // 策略链:实时 → Redis 缓存 → 本地缓存 → 默认值
        return getFromRemote(userId)
            .or(this::getFromRedisCache, userId)
            .or(this::getFromLocalCache, userId)
            .or(this::getDefault, userId)
            .resolve();
    }

    private Optional<RecommendationResult> getFromRemote(Long userId) {
        return Optional.of(recommendationService.getRecommendation(userId));
    }

    private Optional<RecommendationResult> getFromRedisCache(Long userId) {
        String key = "recommendation:" + userId;
        RecommendationResult cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            log.warn("Redis 缓存降级: userId={}", userId);
            return Optional.of(cached);
        }
        return Optional.empty();
    }

    private Optional<RecommendationResult> getFromLocalCache(Long userId) {
        RecommendationResult cached = localCache.getIfPresent(userId);
        if (cached != null) {
            log.warn("本地缓存降级: userId={}", userId);
            return Optional.of(cached);
        }
        return Optional.empty();
    }

    private Optional<RecommendationResult> getDefault(Long userId) {
        log.error("所有降级策略失败,返回默认推荐: userId={}", userId);
        return Optional.of(getHotProducts());
    }
}

降级监控

降级监控指标
# 降级相关指标
degradation:
  - name: fallback_total
    type: counter
    labels: [resource, strategy]
    description: "降级总次数 (redis/local/default)"

  - name: fallback_rate
    type: gauge
    labels: [resource]
    description: "降级率"

  - name: cache_hit_fallback
    type: counter
    description: "缓存命中降级"

  - name: default_fallback
    type: counter
    description: "默认值降级"

# 告警
alerts:
  - name: HighFallbackRate
    condition: fallback_rate > 0.1
    severity: warning
    message: "降级率超过 10%"

本章总结

核心要点

  1. 降级策略有三种:默认值、缓存数据、静态内容
  2. 默认值应该有业务意义:不能随便返回空对象
  3. 缓存降级要处理数据新鲜度:缓存数据可能过时
  4. 空列表 vs 默认对象:根据数据类型选择合适的降级策略
  5. 降级链式策略:实时 → 缓存 → 默认值,逐级降级