服务降级与熔断机制实战:让系统优雅地挂

促销活动开始10分钟,商品服务挂了。

然后呢?订单服务调商品服务超时,线程池打满。用户服务调订单服务超时,线程池也打满。整个系统像多米诺骨牌一样全倒了。

这就是经典的雪崩效应。

解决方案:熔断和降级。

雪崩是怎么发生的

复制代码
用户请求
│
▼
┌─────────┐    调用    ┌─────────┐    调用    ┌─────────┐
│ 用户服务 │ ────────▶ │ 订单服务 │ ────────▶ │ 商品服务 │ ← 挂了
└─────────┘           └─────────┘           └─────────┘
│
▼
线程等待超时
│
▼
线程池满了
│
▼
订单服务也挂了
│
▼
用户服务也挂了

一个服务挂,全链路崩。

熔断器原理

熔断器有三种状态:

sql 复制代码
┌─────────────────────────────────────┐
│                                     │
▼                                     │
┌───────┐  失败率超阈值  ┌───────┐  冷却后  ┌───────────┐
│ 关闭  │ ────────────▶ │ 打开  │ ───────▶ │ 半开      │
│ CLOSED│               │ OPEN  │          │ HALF-OPEN │
└───────┘               └───────┘          └───────────┘
▲                                          │
│            成功率恢复                     │
└──────────────────────────────────────────┘
  • CLOSED:正常状态,所有请求通过
  • OPEN:熔断状态,请求直接失败,不调下游
  • HALF_OPEN:试探状态,放一部分请求过去试试

Sentinel实战

阿里开源的Sentinel,生产环境用得最多。

基本配置

xml 复制代码
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.6</version>
</dependency>
java 复制代码
// 定义资源
@SentinelResource(value = "getProduct", 
    blockHandler = "getProductBlockHandler",
    fallback = "getProductFallback")
public Product getProduct(Long productId) {
    return productService.getById(productId);
}

// 熔断/限流时的处理
public Product getProductBlockHandler(Long productId, BlockException e) {
    log.warn("getProduct被熔断: {}", productId);
    return Product.defaultProduct();  // 返回默认商品
}

// 异常时的降级
public Product getProductFallback(Long productId, Throwable t) {
    log.error("getProduct异常降级: {}", productId, t);
    return Product.defaultProduct();
}

熔断规则

java 复制代码
// 配置熔断规则
DegradeRule rule = new DegradeRule();
rule.setResource("getProduct");
rule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());  // 按错误率熔断
rule.setCount(0.5);  // 错误率50%
rule.setMinRequestAmount(20);  // 最小请求数
rule.setTimeWindow(10);  // 熔断时长10秒
rule.setStatIntervalMs(10000);  // 统计时间窗口

DegradeRuleManager.loadRules(Collections.singletonList(rule));

参数解释:

  • 10秒内请求超过20次,且错误率超过50%,触发熔断
  • 熔断10秒后进入半开状态

限流规则

java 复制代码
FlowRule rule = new FlowRule();
rule.setResource("getProduct");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);  // 按QPS限流
rule.setCount(100);  // 每秒100次

FlowRuleManager.loadRules(Collections.singletonList(rule));

Resilience4j实战

Spring Cloud官方推荐,比Hystrix轻量。

熔断配置

yaml 复制代码
resilience4j:
  circuitbreaker:
    instances:
      productService:
        sliding-window-type: COUNT_BASED
        sliding-window-size: 10
        minimum-number-of-calls: 5
        failure-rate-threshold: 50
        wait-duration-in-open-state: 10s
        permitted-number-of-calls-in-half-open-state: 3

参数解释:

  • 基于最近10次调用统计
  • 至少5次调用才开始计算
  • 失败率超过50%触发熔断
  • 熔断10秒后半开
  • 半开状态放3个请求试探

代码使用

java 复制代码
@CircuitBreaker(name = "productService", fallbackMethod = "getProductFallback")
public Product getProduct(Long productId) {
    return restTemplate.getForObject(
        "http://product-service/products/" + productId, 
        Product.class
    );
}

public Product getProductFallback(Long productId, Exception e) {
    log.warn("商品服务熔断,返回默认值: {}", productId);
    return Product.defaultProduct();
}

组合使用

java 复制代码
@CircuitBreaker(name = "productService", fallbackMethod = "fallback")
@RateLimiter(name = "productService")
@Retry(name = "productService")
@Bulkhead(name = "productService")
public Product getProduct(Long productId) {
    return productService.getById(productId);
}

执行顺序:Retry → CircuitBreaker → RateLimiter → Bulkhead → 实际调用

降级策略

策略一:返回默认值

java 复制代码
public Product getProductFallback(Long productId, Exception e) {
    // 返回一个空商品,让页面能展示
    return Product.builder()
        .id(productId)
        .name("商品加载中...")
        .price(BigDecimal.ZERO)
        .stock(-1)  // -1表示库存未知
        .build();
}

策略二:返回缓存数据

java 复制代码
public Product getProductFallback(Long productId, Exception e) {
    // 从本地缓存取
    Product cached = localCache.get("product:" + productId);
    if (cached != null) {
        cached.setFromCache(true);  // 标记来自缓存
        return cached;
    }
    
    // 缓存也没有,返回默认值
    return Product.defaultProduct();
}

策略三:静态数据兜底

java 复制代码
public List<Product> getHotProductsFallback(Exception e) {
    // 返回预先准备好的静态热门商品
    return staticHotProducts;
}

适合首页推荐、热门榜单这类场景。

策略四:功能降级

java 复制代码
public OrderResult createOrder(Order order) {
    // 正常流程:实时校验库存
    // 降级流程:异步校验,先让订单创建成功
    
    if (isProductServiceDown()) {
        // 商品服务挂了,跳过库存校验
        order.setStockCheckSkipped(true);
        // 发消息异步补偿
        mqTemplate.send("stock-check-later", order);
    }
    
    return orderService.create(order);
}

线程池隔离

另一种防雪崩的方式:线程池隔离。

java 复制代码
@HystrixCommand(
    commandKey = "getProduct",
    threadPoolKey = "productPool",
    threadPoolProperties = {
        @HystrixProperty(name = "coreSize", value = "10"),
        @HystrixProperty(name = "maxQueueSize", value = "20")
    }
)
public Product getProduct(Long productId) {
    return productService.getById(productId);
}

每个服务用独立线程池,一个服务慢不影响其他。

Resilience4j用Bulkhead实现:

yaml 复制代码
resilience4j:
  bulkhead:
    instances:
      productService:
        maxConcurrentCalls: 10  # 最大并发数
        maxWaitDuration: 100ms  # 等待时间

超时配置

超时配置很关键,配错了熔断器不生效。

调用链超时

scss 复制代码
用户 → 网关(10s) → 用户服务(8s) → 订单服务(5s) → 商品服务(3s)

原则:上游超时 > 下游超时

常见配置

yaml 复制代码
# Feign客户端
feign:
  client:
    config:
      default:
        connectTimeout: 2000
        readTimeout: 5000

# RestTemplate
@Bean
public RestTemplate restTemplate() {
    HttpComponentsClientHttpRequestFactory factory = 
        new HttpComponentsClientHttpRequestFactory();
    factory.setConnectTimeout(2000);
    factory.setReadTimeout(5000);
    return new RestTemplate(factory);
}

超时 vs 熔断

markdown 复制代码
请求超时 5s,熔断冷却 10s

场景:商品服务响应变慢(6s)

1. 请求发出
2. 等待5s,超时失败
3. 触发fallback
4. 统计失败率
5. 失败率超阈值,熔断打开
6. 后续请求直接走fallback(不用等5s了)
7. 10s后半开,试探
8. 如果成功,关闭熔断

熔断的意义:快速失败,不浪费时间等超时。

监控告警

熔断了要能看到。

Sentinel Dashboard

bash 复制代码
java -jar sentinel-dashboard-1.8.6.jar --server.port=8080

# 应用接入
java -Dcsp.sentinel.dashboard.server=localhost:8080 \
     -Dproject.name=order-service \
     -jar order-service.jar

Prometheus指标

Resilience4j原生支持Prometheus:

yaml 复制代码
management:
  endpoints:
    web:
      exposure:
        include: health,prometheus,circuitbreakers
promql 复制代码
# 熔断器状态
resilience4j_circuitbreaker_state{name="productService"}

# 失败率
resilience4j_circuitbreaker_failure_rate{name="productService"}

# 调用次数
resilience4j_circuitbreaker_calls_total{name="productService"}

运维实践

我们有几个服务部署在不同城市的机房,需要统一监控熔断状态。用星空组网把各地节点连起来后,Prometheus可以直接采集所有节点的metrics,监控配置简单多了。

总结

熔断降级核心要点:

机制 作用 配置要点
熔断 快速失败 失败率阈值、冷却时间
限流 保护后端 QPS/并发数
降级 用户体验 返回什么数据
隔离 防止蔓延 线程池大小
超时 及时释放 上游>下游

降级策略选择:

策略 适用场景
返回默认值 非核心数据
返回缓存 数据时效性不敏感
静态数据 榜单、推荐位
功能降级 可延后处理的业务
直接失败 核心功能,必须告知用户

系统设计的时候就要想好:哪些功能可以降级,降级后返回什么。别等出事了才想。


熔断降级这块有实战经验的欢迎交流~

相关推荐
明天好,会的13 分钟前
分形生成实验(五):人机协同破局--30万token揭示Actix-web状态管理的微妙边界
运维·服务器·前端
猫头虎36 分钟前
如何在浏览器里体验 Windows在线模拟器:2026最新在线windows模拟器资源合集与技术揭秘
运维·网络·windows·系统架构·开源·运维开发·开源软件
C_心欲无痕43 分钟前
nginx - alias 和 root 的区别详解
运维·前端·nginx
HIT_Weston1 小时前
95、【Ubuntu】【Hugo】搭建私人博客:_default&partials
linux·运维·ubuntu
实心儿儿2 小时前
Linux —— 基础开发工具5
linux·运维·算法
oMcLin2 小时前
如何在SUSE Linux Enterprise Server 15 SP4上通过配置并优化ZFS存储池,提升文件存储与数据备份的效率?
java·linux·运维
SelectDB3 小时前
驾驭 CPU 与编译器:Apache Doris 实现极致性能的底层逻辑
运维·数据库·apache
❀͜͡傀儡师3 小时前
docker部署Arcane容器可视化管理平台
运维·docker·容器
老姚---老姚3 小时前
docker常用命令
运维·docker·容器
深圳安锐科技有限公司4 小时前
边坡倾斜自动化监测 倾角仪 如何通过安锐云查看监测曲线?
运维·视觉检测·实时监测·自动化监测·结构健康监测·倾斜角度监测·倾角传感器