一、Hystrix

前面我们讲解了OpenFeign的使用和注意点,再上节中我们提到了服务降级,服务降级也是一种服务保护的策略,今天我们来讲解下Hystrix 服务保护工具,虽然官方已经声明Hystrix 已经不再更新,但Hystrix 的理念还是值得学习和研究的,了解了hystrix 也可以更好的学习后面的alibab cloud 的Sentinel 框架。


目前面临的问题

目前分布式系统面临的问题,复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的“雪崩效应”。

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

Hystrix的出现

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

Hystrix 的工作流程

spring运行时降低log级别 spring cloud服务降级_spring运行时降低log级别

二、Hystrix 的整合

一般Hystrix 用在消费者端,也可以用在提供者端,我们主要在消费者端进行演示。

首先引入Hystrix 的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

主启动类中添加 @EnableHystrix 注解

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

三、Hystrix 实现服务降级

上面已经引入了Hystrix 的依赖,我们先来体验下服务的降级,编写测试接口:

@RestController
@RequestMapping("/fallback")
public class TestFallBackController {

    @HystrixCommand(fallbackMethod = "fallback")
    @GetMapping("/getTest")
    public ResponseTemplate getTest() throws InterruptedException {
        int a = 1 / 0;
        return ResSuccessTemplate.builder().build();
    }

    public ResponseTemplate fallback() {
        return ResFailTemplate.builder().message("服务降级!").build();
    }
}

启动服务,调用测试接口http://localhost:8080/fallback/getTest

spring运行时降低log级别 spring cloud服务降级_spring cloud_02


现在就已经体验了服务降级的效果了,一般服务降级还有一种场景就是接口调用超时,超时的降级,需要我们配置超时时间等,如果不配置默认1s超时,主要做如下改变:

@RestController
@RequestMapping("/fallback")
public class TestFallBackController {

    @HystrixCommand(fallbackMethod = "fallback")
    @GetMapping("/getTest")
    public ResponseTemplate getTest() throws InterruptedException {
        int a = 1 / 0;
        return ResSuccessTemplate.builder().build();
    }

    @HystrixCommand(fallbackMethod = "fallback",
            commandProperties = {
//                      隔离策略,有THREAD和SEMAPHORE
//                      THREAD - 它在单独的线程上执行,并发请求受线程池中的线程数量的限制
//                      SEMAPHORE - 它在调用线程上执行,并发请求受到信号量计数的限制
                    @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
//                    开启超时时间,默认值:true
                    @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
//                    超时时间
//                    默认值:1000,在THREAD模式下,达到超时时间,可以中断
//                    在SEMAPHORE模式下,会等待执行完成后,再去判断是否超时
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
//                    在发生超时时是否应中断,默认值:true,THREAD模式有效
                    @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
            }
    )
    @GetMapping("/getTimeOut")
    public ResponseTemplate getTimeOut() throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        return ResSuccessTemplate.builder().build();
    }

    public ResponseTemplate fallback() {
        return ResFailTemplate.builder().message("服务降级!").build();
    }
}

上面我们添加了新的接口,并配置了隔离策略和超时策略,需要注意的是,线程池的隔离可以中断执行方法,信号量的需要等待完成,下面在浏览器调用http://localhost:8080/fallback/getTimeOut 测试

spring运行时降低log级别 spring cloud服务降级_java_03


大家也可以把隔离策略换成信号量再测试,可以发现是等待5s后但会的服务降级结果。

但是现在大家应该能发现一个问题,就是我们没写一个接口都要声明@HystrixCommand以及其中的好多参数,比如超时时间和隔离策略我们对全部的接口都适用,此时我们就可以把配置写到配置文件中:

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true #是否应该有超时
        isolation: # 隔离策略
          #隔离策略,有THREAD 、SEMAPHORE,
          #THREAD - 它在单独的线程上执行,并发请求受线程池中的线程数量的限制
          #SEMAPHORE - 它在调用线程上执行,并发请求受到信号量计数的限制
          strategy: THREAD
          semaphore:  #信号量
            maxConcurrentRequests: 100  #配置信号量的大小,当最大并发请求数达到该设置值,后续的请求将会被拒绝
          thread:
            timeoutInMilliseconds: 2000 #服务调用超时时间,THREAD隔离模式下是请求超时是会取消调用线程从而立即返回的,SEMAPHORE模式下会等待响应回来再判断是否超时。
            interruptOnTimeout: true   #执行超时的时候,是否需要将他中断
            interruptOnCancel: true  #是否在方法执行被取消时中断方法

在服务的接口只需添加 @HystrixCommand(fallbackMethod = "fallback") 即可,再次调用接口,可以发现是一样的效果:

@HystrixCommand(fallbackMethod = "fallback")
 @GetMapping("/getTimeOut")
 public ResponseTemplate getTimeOut() throws InterruptedException {
     TimeUnit.SECONDS.sleep(5);
     return ResSuccessTemplate.builder().build();
 }

spring运行时降低log级别 spring cloud服务降级_spring cloud_04


到这还是有一个问题,我们每个接口又都需要指定fallback方法,如果又100个接口方法,哪岂不要写100个降级方法了,此时我们可以使用@DefaultProperties来为整个Controller 指定一个默认的降级方法,如果@HystrixCommand没有指定方法就使用@DefaultProperties的,下面修改我们的程序:

@RestController
@RequestMapping("/fallback")
@DefaultProperties(defaultFallback = "fallback")
public class TestFallBackController {

    @HystrixCommand
    @GetMapping("/getTest")
    public ResponseTemplate getTest() throws InterruptedException {
        int a = 1 / 0;
        return ResSuccessTemplate.builder().build();
    }

    @HystrixCommand
    @GetMapping("/getTimeOut")
    public ResponseTemplate getTimeOut() throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        return ResSuccessTemplate.builder().build();
    }

    public ResponseTemplate fallback() {
        return ResFailTemplate.builder().message("服务降级!").build();
    }
}

调用其中一个接口,可以看出使用了fallback降级方法。

spring运行时降低log级别 spring cloud服务降级_spring运行时降低log级别_05