什么是服务降级?
结论:服务降级(Service Degradation)是在系统压力过大或依赖故障时,主动关闭或简化非核心功能,把有限资源让给核心链路,保证主流程可用。它与熔断的分工是:熔断是对某个依赖停止调用,降级是对功能做取舍;熔断往往触发降级。
一、降级的四种形态
| 形态 | 做法 | 例子 |
|---|---|---|
| 读降级 | 返回兜底值、缓存旧值、默认推荐 | 推荐服务挂了就返回热销榜 |
| 写降级 | 写转异步、只记流水 | 下单成功后积分异步发放 |
| 功能降级 | 关闭非核心入口 | 大促关闭评价、晒单、优惠券叠加 |
| 限流降级 | 超阈值直接拒绝并提示 | 秒杀排队页 |
二、分级与决策
请求
│
┌──────▼───────┐
│ 是否核心链路 │── 否 ──► 直接降级(关闭或兜底)
└──────┬───────┘
│ 是
┌──────▼───────┐
│ 依赖是否可用 │── 否 ──► 熔断 + 兜底数据
└──────┬───────┘
│ 是
┌──────▼───────┐
│ 容量是否足够 │── 否 ──► 限流 + 排队 + 降级
└──────┬───────┘
│ 是
正常处理
| 等级 | 含义 | 降级策略 |
|---|---|---|
| P0 | 下单、支付 | 不降级,只限流与扩容 |
| P1 | 订单查询、物流 | 降级为缓存读或稍后重试 |
| P2 | 推荐、评价、积分 | 可关闭,返回空或默认值 |
三、开关与兜底实现
@Component
public class RecommendService {
private final SwitchService switches; // 配置中心,秒级推送
private final RecommendClient client;
public List<Item> recommend(long userId) {
if (!switches.isOn("feature.recommend")) {
return fallbackHotItems(); // 功能降级:直接给热销
}
try {
return client.fetch(userId);
} catch (Exception e) {
metrics.counter("recommend.fallback").increment();
return fallbackHotItems(); // 异常降级
}
}
private List<Item> fallbackHotItems() {
List<Item> hot = localCache.get("hot_items");
return hot == null ? Collections.emptyList() : hot; // 本地缓存兜底
}
}
# 配置中心下发的降级开关
degrade:
feature.recommend: false
feature.comment: false
order.query.timeout-ms: 300
pay.fallback.status: PROCESSING
四、降级的五个设计要点
- 开关秒级生效,且本地留一份兜底配置,配置中心不可达时仍能按默认值运行。
- 兜底数据提前准备,不能等降级了再去查库。
- 降级可观测:打点、日志、告警齐备,否则降级了没人知道。
- 必须演练:大促前用开关做一次降级演练,验证兜底逻辑真的可用。
- 能自动恢复:人工开关容易忘记打开,需配合自动探测与定时复位。
五、常见追问
Q1:降级和熔断的区别? 熔断是对依赖的调用被切断,是自动的、基于指标;降级是功能层面的取舍,可以自动也可以人工。熔断后通常走降级逻辑,二者串联使用。
Q2:降级后数据一致性怎么保证? 写降级要把请求落到消息队列或本地表,异步补偿;读降级返回旧值时要标注「可能延迟」,避免用户据此做决策。
Q3:怎么防止降级开关本身失效? 开关配置在本地缓存一份,配置中心不可达时使用最后一次已知值;关键开关做成默认开启降级的 fail-safe 模式。
Q4:降级和限流谁先触发? 通常限流在前:先按阈值拒绝超额流量,再由熔断判断依赖健康度,最后才是功能降级。顺序反了会出现「还有容量却先关功能」。
小结
- 降级是资源紧张时的功能取舍,核心链路永远不降级。
- 开关、兜底数据、可观测、演练,四件套缺一不可。
- 熔断自动、降级可人工,二者通常串联使用。