在微服务架构中服务熔断和降级是保证系统高可用的重要手段。

当某个服务出现故障,或者响应慢的时候,如果没有熔断和降级机制请求会不断地发送到故障服务导致线程资源被耗尽,进而影响其他服务最终导致整个系统崩溃这就是雪崩效应。

熔断和降级能防止故障蔓延避免雪崩效应保证核心功能的可用性。

我最近在项目中做服务熔断降级的配置踩了一些坑也积累了一些经验。今天想详细讲解服务熔断降级的配置从基础到高级帮大家掌握这门技术。

一、什么是熔断和降级

在讲配置之前,先理解什么是熔断和降级。

熔断(Circuit Breaker)

熔断的概念来自电路的保险丝。当电路中电流过大的时候,保险丝会熔断切断电路保护电器不被烧坏。

在软件系统中熔断是类似的机制。当某个服务的错误率,或者响应时间超过阈值的时候,熔断器会打开切断对该服务的调用直接返回降级结果,或者错误保护系统不被拖垮。

熔断器有三种状态:

  • 关闭(Closed):正常状态请求正常发送到服务。
  • 打开(Open):熔断状态请求不发送到服务直接返回降级结果。
  • 半开(Half-Open):试探状态允许少量请求发送到服务试探服务是否恢复。

状态转换逻辑:

  1. 关闭状态下统计请求的错误率和响应时间。
  2. 当错误率,或者响应时间超过阈值熔断器打开。
  3. 打开状态持续一段时间后进入半开状态。
  4. 半开状态下允许少量请求通过,如果这些请求都成功熔断器关闭恢复正常。如果有请求失败熔断器继续打开。

降级(Degradation)

降级是当系统出现问题的时候,有意识地关闭,或者简化一些非核心功能保证核心功能的可用性。

比如电商系统在大促的时候,流量很大可以暂时关闭商品评价推荐等非核心功能保证下单支付等核心功能的正常。

降级和熔断的区别:

  • 熔断是被动的当服务出现故障的时候,自动触发。
  • 降级是主动的可以根据系统负载,或者业务需要主动触发。

熔断和降级经常一起使用熔断触发后通常会执行降级逻辑返回降级结果。

二、为什么需要熔断降级

在微服务架构中熔断降级是必须的,因为:

1. 防止雪崩效应

微服务架构中服务之间,相互调用形成调用链。如果某个服务出现故障响应慢,或者不响应调用方的线程会被阻塞等待响应。如果大量请求都阻塞在这里调用方的线程资源会被耗尽导致调用方也无法响应其他请求,进而影响更上游的服务最终导致整个系统崩溃这就是雪崩效应。

熔断能在服务出现故障的时候,快速切断调用不等待响应避免线程被阻塞防止雪崩效应。

2. 保证核心功能可用

当系统负载很高,或者出现故障的时候,降级能主动关闭非核心功能释放资源保证核心功能的可用性。

比如电商系统下单支付是核心功能评价推荐是非核心功能。在大促的时候,可以关闭评价推荐保证下单支付的正常。

3. 提升用户体验

熔断降级能在系统出现问题的时候,给用户一个友好的提示,或者降级的结果而不是长时间等待,或者报错提升用户体验。

比如商品推荐服务故障的时候,可以返回热门商品而不是报错,或者长时间白屏。

4. 系统自我保护

熔断降级是系统的自我保护机制能在系统面临危险的时候,自动保护自己避免更严重的后果。

三、Hystrix配置详解

Hystrix是Netflix开源的熔断降级框架是目前最流行的熔断降级方案之一。Spring Cloud也集成了Hystrix。

下面详细讲解Hystrix的配置。

1. 引入依赖

如果用Maven引入Hystrix依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

如果用Gradle:

compile 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'

2. 启用Hystrix

在启动类上添加@EnableCircuitBreaker注解启用熔断:

@SpringBootApplication
@EnableCircuitBreaker
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

或者用@EnableHystrix注解效果一样。

3. 配置熔断降级

在需要熔断的方法上添加@HystrixCommand注解配置熔断参数和,降级方法:

@Service
public class UserService {
    
    @HystrixCommand(
        fallbackMethod = "getUserFallback",
        commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")
        }
    )
    public User getUser(Long id) {
        // 调用用户服务
        return userServiceClient.getUser(id);
    }
    
    public User getUserFallback(Long id) {
        // 降级逻辑返回默认用户
        User user = new User();
        user.setId(id);
        user.setName("默认用户");
        return user;
    }
}

4. 核心配置参数详解

Hystrix的配置参数很多下面讲解最核心的几个:

execution.isolation.thread.timeoutInMilliseconds

  • 超时时间单位毫秒默认1000。
  • 调用超过这个时间会被认为超时触发降级。
  • 根据服务的实际响应时间设置不要太大也不要太小。

execution.isolation.strategy

  • 隔离策略可选THREAD(线程隔离)或SEMAPHORE(信号量隔离)默认THREAD。
  • 线程隔离用独立的线程池执行调用和调用方线程隔离,即使调用阻塞也不会影响调用方。
  • 信号量隔离用信号量控制并发数在调用方线程执行开销小,但是不能处理长时间阻塞。
  • 一般推荐用线程隔离更安全。

circuitBreaker.requestVolumeThreshold

  • 熔断触发的最小请求数默认20。
  • 在一个统计窗口内请求数少于这个值,即使全部失败也不会触发熔断。
  • 防止,因为少量请求失败就误触发熔断。

circuitBreaker.errorThresholdPercentage

  • 熔断触发的错误率阈值百分比默认50。
  • 在一个统计窗口内请求数达到requestVolumeThreshold且错误率超过这个百分比触发熔断。

circuitBreaker.sleepWindowInMilliseconds

  • 熔断打开后休眠时间单位毫秒默认5000。
  • 熔断打开后持续这个时间,然后进入半开状态试探服务是否恢复。

metrics.rollingStats.timeInMilliseconds

  • 统计窗口时间单位毫秒默认10000。
  • 熔断的错误率统计基于这个窗口。

metrics.rollingStats.numBuckets

  • 统计窗口的桶数默认10。
  • 统计窗口被分成这么多桶每个桶统计一段时间的数据。

5. 线程池配置

Hystrix用线程池隔离每个命令可以配置独立的线程池:

@HystrixCommand(
    fallbackMethod = "getUserFallback",
    threadPoolKey = "userServiceThreadPool",
    threadPoolProperties = {
        @HystrixProperty(name = "coreSize", value = "10"),
        @HystrixProperty(name = "maxQueueSize", value = "20"),
        @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15")
    }
)
public User getUser(Long id) {
    // ...
}

核心参数:

  • coreSize:核心线程数默认10。
  • maxQueueSize:最大队列大小默认-1(不排队)。
  • queueSizeRejectionThreshold:队列拒绝阈值默认5。即使队列没满达到这个阈值也会拒绝。

合理配置线程池能防止某个服务的调用占用太多线程影响其他服务。

6. 全局配置

除了在注解上配置也可以在application.yml中全局配置:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000
      circuitBreaker:
        requestVolumeThreshold: 20
        errorThresholdPercentage: 50
        sleepWindowInMilliseconds: 5000
  threadpool:
    default:
      coreSize: 10
      maxQueueSize: 20

全局配置对所有HystrixCommand生效也可以针对某个命令单独配置。

四、Sentinel配置详解

Sentinel是阿里巴巴开源的熔断降级框架是Hystrix的替代方案功能更强大配置更灵活支持动态规则控制台等。

下面简单讲解Sentinel的配置。

1. 引入依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2. 配置Sentinel

在application.yml中配置Sentinel控制台地址:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080

3. 定义资源和降级

用@SentinelResource注解定义资源和降级方法:

@Service
public class UserService {
    
    @SentinelResource(value = "getUser", fallback = "getUserFallback")
    public User getUser(Long id) {
        return userServiceClient.getUser(id);
    }
    
    public User getUserFallback(Long id) {
        User user = new User();
        user.setId(id);
        user.setName("默认用户");
        return user;
    }
}

4. 配置规则

Sentinel的规则可以在控制台动态配置也可以通过代码配置。

Sentinel支持多种规则:

  • 流量控制规则:控制请求的QPS或者并发数。
  • 熔断降级规则:根据响应时间异常率异常数触发熔断。
  • 系统保护规则:根据系统负载CPU等保护系统。
  • 访问控制规则:根据调用方黑白名单控制访问。

熔断降级规则配置示例:

private void initDegradeRule() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule();
    rule.setResource("getUser");
    rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
    rule.setCount(0.5); // 异常率50%
    rule.setTimeWindow(10); // 熔断时间10秒
    rule.setMinRequestAmount(20); // 最小请求数
    rules.add(rule);
    DegradeRuleManager.loadRules(rules);
}

Sentinel比Hystrix更灵活功能更强大,而且有控制台能动态配置规则查看监控是现在更推荐的方案。

五、高级配置和最佳实践

讲完了基础配置再讲讲高级配置和最佳实践。

1. 合理设置超时时间

超时时间的设置很重要。设置太大起不到保护作用线程会被长时间阻塞。设置太小会误杀正常的慢请求导致不必要的降级。

应该根据服务的实际响应时间设置一般设置为P99响应时间的1.5-2倍。可以通过监控统计服务的响应时间,然后合理设置。

2. 合理设置熔断阈值

熔断阈值的设置也很重要。requestVolumeThreshold太小会,因为少量请求失败就误触发熔断。太大会导致熔断触发不及时起不到保护作用。

errorThresholdPercentage太小会过于敏感稍微有一些错误就熔断。太大会不敏感错误率很高才熔断起不到保护作用。

应该根据服务的实际情况设置一般requestVolumeThreshold设为20-50errorThresholdPercentage设为50%。

3. 降级逻辑要合理

降级逻辑要合理不能太简单也不能太复杂。

太简单,比如直接返回null或者空列表可能会导致上游服务出错,或者用户体验差。

太复杂,比如降级逻辑又调用其他服务可能会导致新的故障点。

降级逻辑应该返回合理的默认值,或者缓存数据,或者友好的提示保证上游服务能正常处理用户体验不太差。

而且降级逻辑本身要简单可靠不能再依赖其他服务,否则降级也会失败。

4. 区分核心和非核心功能

不是所有功能都需要熔断降级要区分核心和非核心功能。

核心功能,比如下单支付登录等等要重点保护熔断降级的配置要更谨慎降级逻辑要更完善。

非核心功能,比如推荐评价广告等等可以更激进地熔断降级甚至直接关闭保证核心功能的资源。

5. 监控和告警

熔断降级的配置不是一劳永逸的需要监控和告警及时发现问题调整配置。

要监控熔断器的状态触发次数降级次数请求成功率响应时间等等。当熔断频繁触发,或者错误率升高的时候,要及时告警排查问题。

Hystrix和Sentinel都提供了监控的支持可以配合Dashboard或者,PrometheusGrafana做监控和告警。

6. 测试熔断降级

熔断降级的逻辑要测试确保在故障的时候,能正确触发降级逻辑能正常工作。

可以用故障注入的方式测试,比如模拟服务超时报错不可用等等看熔断是否正确触发降级是否正确执行。

7. 避免熔断风暴

如果很多服务,同时熔断可能会导致熔断风暴系统大量降级影响用户体验。

要合理设置熔断参数避免过于敏感导致大量服务,同时熔断。也要有系统级别的保护,比如限流系统负载保护等等防止系统被打垮。

六、常见问题和踩坑

最后讲讲常见的问题和我踩过的坑。

坑1:降级方法参数不匹配

Hystrix的fallbackMethod参数必须和原方法一致,否则会报错。这个是新手常犯的错误。

比如原方法是getUser(Long id)降级方法也必须是getUserFallback(Long id)参数类型和数量都要一致。

坑2:降级方法和原方法在同一个类

Hystrix的fallbackMethod默认是在同一个类中查找。如果降级方法在其他类需要用fallback属性指定类。

而且降级方法的访问修饰符没有限制private也可以。

坑3:Hystrix线程池隔离导致ThreadLocal失效

Hystrix默认用线程池隔离调用在新的线程执行,所以ThreadLocal中的数据会丢失,比如用户上下文请求上下文等等。

如果需要传递ThreadLocal数据可以用信号量隔离,或者自定义Hystrix的并发策略传递ThreadLocal。

坑4:超时时间设置不合理

很多人超时时间设置得太长,或者太短导致熔断起不到作用,或者误触发。

一定要根据服务的实际响应时间设置通过监控统计P99响应时间,然后合理设置。

坑5:降级逻辑又调用远程服务

降级逻辑又调用远程服务导致降级也失败起不到降级的作用。

降级逻辑应该简单可靠返回默认值,或者缓存数据不要再依赖其他服务。

坑6:所有方法都加熔断

不是所有方法都需要加熔断,只有调用远程服务的方法,或者可能慢的方法才需要加熔断。

给本地的简单方法加熔断是没有意义的反而增加开销。

七、写在最后

以上就是服务熔断降级配置的详细讲解从基础到高级。

熔断降级是微服务架构中保证系统高可用的重要手段每个后端开发都应该掌握。

配置熔断降级不是一件简单的事情需要理解原理合理设置参数完善降级逻辑做好监控和,测试。

希望这篇文章能帮大家掌握服务熔断降级的配置构建高可用的微服务系统。

如果有什么问题,或者不同的看法欢迎在评论区留言我们一起交流。

最后用一句话结束这篇文章:"熔断降级不是为了让系统不出错而是为了让系统在出错的时候,还能活着。"

愿大家都能构建稳定高可用的系统。