Spring Cloud之容错组件Hystrix

1. Hystrix介绍

1.1 初识Hystrix

Hystrix是Netflix开源的容错框架,这是Netflix对其的简短介绍:

Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

可以大致这样理解:分布式系统间的服务调用,不可避免的会出错,Hystrix提供了一套系统的容错、处理错误的方法,来尽量保证你的分布式系统的稳定性。

以下面这个常见的电商系统服务调用为例:

image

假如我们现在有上面三个核心服务:用户服务、订单服务、库存服务,三个服务均是通过HTTP调用。
image

假如库存系统挂了,造成很多的线程阻塞,若后面有很多其他的HTTP请求,将很快造成线程池耗尽,则整个服务对外不可用,这就是微服务系统的雪崩效应。

image

1.2 系统容错

对于1.1中的案例,依赖Hystrix,可以提供一套依赖服务的治理和监控解决方案,Hystrix包含常用的容错方法:线程池隔离、信号量隔离、熔断、降级等。

2. Spring Cloud整合Hystrix

2.1 创建整合hystrix的项目

首先创建一个空的Spring Boot项目,在pom.xml中加入如下引用:

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
30
31
32
33
34
35
36
37
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

其中spring-cloud-starter-hystrix-dashboard是查看hystrix状态的组件,如不用可不添加。

主函数中启动hystrix:

1
2
@EnableCircuitBreaker
@EnableHystrixDashboard

其中@EnableHystrixDashboard是查看hystrix状态的组件,如不用可不添加。

增加hystrix.stream的servlet,这个同时也是查看hystrix状态的组件,如不用可不添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 配置hystrix.stream
*
* @return
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}

创建一个基本的RestController类调用示例:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import com.alibaba.fastjson.JSONObject;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by zhaoyh on 2019-07-30
*
* @author zhaoyh
*/
@RestController
@Slf4j
public class TestController {
/**
* 超时时间设置为1000ms
*
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "onErrors",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")})
@GetMapping("/getName/{id}")
public JSONObject getName(@PathVariable("id") long id) {
JSONObject json = new JSONObject();
// 在这里构造出错的机会,触发fallback
if (id < 0) {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
log.error("", e);
}
}
json.put("name", "name_" + id);
return json;
}
@HystrixCommand(fallbackMethod = "onErrors")
@GetMapping("/getAge/{id}")
public JSONObject getAge(@PathVariable("id") long id) {
JSONObject json = new JSONObject();
// 在这里构造出错的机会,触发fallback
if (id < 0) {
throw new RuntimeException("msg");
}
json.put("age", "age_" + id);
return json;
}
/**
* 如果fallback方法的参数和原方法参数个数不一致
* 则会出现FallbackDefinitionException: fallback method wasn't found
*
* @param id
* @return
*/
public JSONObject onErrors(long id) {
JSONObject json = new JSONObject();
json.put("id", id);
json.put("msg", "执行你需要的默认方法!");
return json;
}
}

最后,application.properties中的配置为简单的:

1
2
server.port=16091
spring.application.name=springcloud-hystrix-test

2.2 测试

启动项目,首先打开 http://localhost:16091/hystrix/ ,这是初始的查看hystrix状态的Dashboard:

image

当然,如果你在2.1中没有添加spring-cloud-starter-hystrix-dashboard引用,是没有这个页面的。

http://localhost:16091/hystrix.stream 链接贴到dashboard里,命个名之后,就可以看到hystrix的请求数据流了:
image

多请求几次定义的接口,即可看到dashboard有数据了:

image

2.3 触发断路器

请求 http://localhost:16091/getName/-22 ,会抛出异常,模仿我们日常的程序出错:

image

多执行几次,然后查看dashboard,发现断路器是open的状态:

image

断路器是open的状态后再请求 http://localhost:16091/getName/-22 都会执行默认的fallback方法,你可以在fallback方法里重写出错后的返回逻辑。

断路器是closed的状态后所有请求恢复正常。

3. Hystrix高级篇

3.1 理解熔断和降级

理解熔断:以家用电路保险丝为例,当电流过载时,保险丝会自动断掉,以此来保护家用电器。Hystrix中的断路器也是类似的功能,当达到开关阈值时,断路器打开,此时所有请求执行fallback方法;当达到断路器可以关闭的状态时,熔断器closed,此时所有的请求再恢复正常。Hystrix的熔断器状态转换图如下:

image

熔断器打开和关闭,都是可配置的,目前,默认的打开和关闭的策略可查询源代码

1
2
3
4
5
static final Integer default_metricsRollingStatisticalWindow = 10000;
private static final Integer default_metricsRollingStatisticalWindowBuckets = 10;
private static final Integer default_circuitBreakerRequestVolumeThreshold = 20;
private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000;
private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;

即:10秒的窗口期内,至少请求20次,且出错比例超过50%,则触发熔断器打开。

半开状态的试探休眠时间默认值5000ms。当熔断器open一段时间之后比如5000ms,会尝试放过去一部分流量进行试探,确定依赖服务是否恢复。

理解降级:可以把降级理解为fallback机制,当服务出错时,执行fallback备用方法,可以称为降级机制。

3.2 线程隔离

Hystrix使用命令模式HystrixCommand(Command)包装依赖调用逻辑,每个命令在单独线程中/信号授权下执行。并将封装好的命令和线程池的对应关系放到一个ConcurrentHashMap中,这样,每个请求使用独立的线程池处理请求,保证了互相之间不受影响,避免了一个服务出问题后大量线程阻塞的问题。通过设置线程池的合理大小来控制并发访问量,当线程饱和的时候可以拒绝服务,防止依赖问题扩散。

线程隔离的优点:

  • 将各个服务保护起来,即使依赖的一个服务的线程池满了,也不会影响到应用程序的其他部分。
  • 可提供内置的并发功能,使得可以在同步调用之上构建异步的外观模式,这样就可以很方便的做异步编程。
  • 参数可配置,比如延迟、超时、拒绝等。同时可以通过动态属性实时执行来处理纠正错误的参数配置。
  • 通过配置合理的线程池大小,实现访问限流。

3.3 信号量隔离

当我们依赖的服务是极低延迟的,比如访问内存缓存,就没有必要使用线程池的方式,那样的话开销得不偿失,而是推荐使用信号量这种方式。
将属性execution.isolation.strategy设置为SEMAPHORE 就实现了信号量隔离策略,

线程隔离和信号量隔离的不同可见下图:

image

线程隔离策略下业务请求线程和执行调用服务的线程不是同一个线程;信号量方式下业务请求线程和执行调用服务的线程是同一个线程。

信号量隔离的方式是限制了总的并发数,每一次请求过来,请求线程和调用依赖服务的线程是同一个线程,那么如果不涉及远程RPC调用(没有网络开销)则使用信号量来隔离,更为轻量,开销更小。

4. 参考文档

  1. Spring Cloud Netflix
  2. Hystrix源码解析
  3. Hystrix 分布式系统限流、降级、熔断框架
  4. springcloud(四):熔断器Hystrix

以上内容就是关于Spring Cloud之容错组件Hystrix的全部内容了,谢谢你阅读到了这里!

Author:zhaoyh