SpringCloud—Hystrix熔断与降级

Hystrix是什么

我们知道,在复杂的分布式架构系统中,存在许许多多的微服务,各个服务之间会存在相互调用,有的甚至有很长的调用链。当其中某一个服务I突然故障不可用时,会导致其他调用它的服务都会等待I的响应,这些服务会出现请求堆积,线程阻塞,慢慢扩散到所有微服务,引发雪崩效应。

为了防止整个系统被拖垮,我们需要对每个服务采取隔离措施。
Spring Cloud Netflix Hystrix就是隔离措施的一种实现,它可以设置在某种超时或者失败情形下断开依赖调用或者返回指定逻辑,从而提高分布式系统的稳定性。

Hystrix设计原则

  1. 防止单个服务的故障,耗尽整个系统服务的容器(比如tomcat)的线程资源,避免分布式环境里大量级联失败。通过第三方客户端访问(通常是通过网络)依赖服务出现失败、拒绝、超时或短路时执行回退逻辑。

  2. 用快速失败代替排队(每个依赖服务维护一个小的线程池或信号量,当线程池满或信号量满,会立即拒绝服务而不会排队等待)和优雅的服务降级;当依赖服务失效后又恢复正常,快速恢复。

  3. 提供接近实时的监控和警报,从而能够快速发现故障和修复。监控信息包括请求成功,失败(客户端抛出的异常),超时和线程拒绝。如果访问依赖服务的错误百分比超过阈值,断路器会跳闸,此时服务会在一段时间内停止对特定服务的所有请求。

  4. 将所有请求外部系统(或请求依赖服务)封装到HystrixCommandHystrixObservableCommand对象中,然后这些请求在一个独立的线程中执行。使用隔离技术来限制任何一个依赖的失败对系统的影响。每个依赖服务维护一个小的线程池(或信号量),当线程池满或信号量满,会立即拒绝服务而不会排队等待。

Hystrix特性

  1. 请求熔断: 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open)。这时所有请求会直接失败而不会发送到后端服务。
    断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN),这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN)。
    Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力。

  2. 服务降级:Fallback相当于是降级操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值。 fallback方法的返回值一般是设置的默认值或者来自缓存,用来告知后面的请求服务不可用了,暂时不要再来请求了。

  3. 依赖隔离(采用舱壁模式,Docker就是舱壁模式的一种):在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池。
    比如说,一个服务调用两外两个服务,你如果调用两个服务都用一个线程池,那么如果一个服务卡在哪里,资源没被释放。后面的请求又来了,导致后面的请求都卡在哪里等待,导致你依赖的A服务把你卡在哪里,耗尽了资源,也导致了你另外一个B服务也不可用了。这时如果依赖隔离,某一个服务调用A B两个服务,如果这时我有100个线程可用,我给A服务分配50个,给B服务分配50个,这样就算A服务挂了, 我的B服务依然可以用。

  4. 请求缓存:比如一个请求过来请求我userId=1的数据,你后面的请求也过来请求同样的数据,这时我不会继续走原来的那条请求链路了,而是把第一次请求缓存过了,把第一次的请求结果返回给后面的请求。

  5. 请求合并:我依赖于某一个服务,我要调用N次,比如说查数据库的时候,我发了N条请求发了N条SQL然后拿到一堆结果,这时候我们可以把多个请求合并成一个请求,发送一个查询多条数据的SQL的请求,这样我们只需查询一次数据库,提升了效率。

Hystrixl流程图

流程说明:
1:每个请求都会封装到 HystrixCommand 中
2:请求会以同步或异步的方式进行调用
3:判断熔断器是否打开,如果打开,它会直接跳转到 8 ,进行降级
4:判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8
5:如果前面没有错误,就调用 run 方法,运行依赖逻辑
5:运行方法可能会超时,超时后从 5a 到 8,进行降级
6:运行过程中如果发生异常,会从 6b 到 8,进行降级
6:运行正常会进入 6a,正常返回回去,同时把错误或正常调用结果告诉 7 (Calculate Circuit Health)
7:Calculate Circuit Health它是 Hystrix 的大脑,是否进行熔断是它通过错误和成功调用次数计算出来的
8:降级方法(8a没有实现降级、8b实现降级且成功运行、8c实现降级方法,但是出现异常)
8a:没有实现降级方法,直接返回异常信息回去
8b:实现降级方法,且降级方法运行成功,则返回降级后的默认信息回去
8c:实现降级方法,但是降级也可能出现异常,则返回异常信息回去

helloworld

下面演示一下Hystrix使用的简单示例。

  1. 引入依赖:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <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>
  2. applicatoin.yml配置文件

    1
    2
    3
    4
    5
    server:
    port: 8020
    spring:
    application:
    name: hystrix
  3. 启动引导类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @SpringBootApplication
    @EnableHystrix //开启Hystrix
    public class HystrixBootstrap {

    public static void main(String[] args) {
    SpringApplication.run(HystrixBootstrap.class, args);
    }


    }
  4. controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    @RestController
    @RequestMapping("/hystrix")
    public class HystrixController {



    @RequestMapping("/hello")
    //通过HystrixCommand注解,手动指定一个降级方法,出现异常后会调用该降级方法
    @HystrixCommand(fallbackMethod = "helloFallback")
    public String hello(@RequestParam String name) throws Exception {
    if ("zhangsan".equals(name)) {
    return String.format("hello %s !", name);
    } else {
    throw new Exception();
    }
    }


    /**
    * 出错后会调用该降级方法,返回指定的信息
    *
    * @param name
    * @return
    */
    public String helloFallback(String name) {
    return "this username is not exist ";
    }

    }

运行程序,访问 http://http://localhost:8020/hystrix/hello?name=zhangsan ,正常,没有触发降级。

再访问 http://http://localhost:8020/hystrix/hello?name=zhangsan000 ,可以看到返回了降级后设置的信息。

至此,使用Hystrix最简单的降级代码完成了。

需要注意的是,Hystrix通常会搭配Feign或者RestTemplate使用,搭配Feign的话@HystrixCommand一般是标注在FeignClient的接口方法上。

参数配置

上面仅演示了Hystrix的降级简单示例,Hystrix还有很多其他配置,如:指定HystrixCommand的超时时间、线程池key以及线程池和队列大小等。

1
2
3
4
5
6
7
8
@HystrixCommand(fallbackMethod = "helloFallback", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "4000")
},
threadPoolKey = "hystrixDemoThreadPool",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "10")
})

下面就来介绍一下其他的一些常用配置参数。
@HystrixCommand注解中除了配置fallbackMethod,还可以配置如下:
groupKey: 命令组名,用于将HystrixCommand分组,默认是用被注解的方法所在的类名(getClass().getSimpleName();)。
commandKey:命令名称,每个CommandKey代表一个依赖抽象,相同的依赖要使用相同的CommandKey名称。依赖隔离的根本就是对相同CommandKey的依赖做隔离。
threadPoolKey:所属线程池的名称,同样配置的命令会共享同一线程池。
commandProperties:用来设置 HystrixCommand 的一些属性。比如下面的这个

1
2
3
4
5
6
7
8
9
10
@HystrixCommand(fallbackMethod = "getNameFallback", commandProperties = {
// 开启断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 请求次数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
// 时间窗口期
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
// 失败率
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
})
表示在10000ms内,如果请求达到了10次,并且60%以上的请求都失败了,Hystrix将会开启熔断。

commandProperties的所有配置属性请参考HystrixCommandProperties类。

threadPoolProperties:用来设置 HystrixCommand 线程池的一些属性。比如下面这个:

1
2
3
4
5
6
7
8
@HystrixCommand(fallbackMethod = "getNameFallback", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "4000")
},
threadPoolKey = "hystrixDemoThreadPool",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "1000")
})
表示线程池核心线程是30,任务队列的最大容量是1000。

threadPoolProperties的所有配置属性请参考HystrixThreadPoolProperties类。

------ 本文完 ------