服务雪崩:
如果扇出的链路上某个微服务的响应超时或者不可用,对微服务调用方的调用会占用越来越多的系统资源,进而引起系统崩溃,这就是"雪崩效应"。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和,比失败更糟糕的是,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不会导致整个应用程序或系统崩溃。
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
1. 熔断、降级、限流
1.1 熔断
先介绍一下断路器,断路器本身是一种开关装置,而服务熔断指的就是断路器打开。
当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的,可处理的备选响应(降级FallBack),而不是长时间的等待或者抛出调用方法处理的异常,当检测到该节点微服务调用响应正常后恢复调用链路。
断路器工作过程:服务的降级->进而熔断->恢复调用链路
- 请求达到统计时长(metrics.rollingStats.timeInMilliseconds 统计滚动窗口的持续时间)
- 假设电路上的请求量达到某个阈值(circuitBreaker.requestVolumeThreshold 最小请求数)
- 并假设误差百分比超过阈值误差百分比(circuitBreaker.errorThresholdPercentage 失败率达到多少后开启)
- 然后,断路器从转换CLOSED为OPEN。
- 当它断开时,它会使针对该断路器的所有请求短路。
- 经过一段时间(circuitBreaker.sleepWindowInMilliseconds 跳闸后拒绝请求的时间)后,下一个单个请求被允许通过(这是HALF-OPEN状态)。
如果请求失败,继续进入步骤5,则断路器将OPEN在睡眠窗口持续时间内返回到该状态。
如果请求成功,断路器将切换到,CLOSED并且步骤1中的逻辑将再次接管。
1.2 服务降级
服务降级:简单的说就是当当前服务有问题(异常或超时)时,向调用方返回一个符合预期的,可处理的备选响应。
哪些情况会触发降级:
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
1.3 限流
Hystrix中的限流,一般表现为线程池/信号量满负荷后,进入降级方法,进而达到限流的效果。
说明:
本文的所有代码,都是在前面文章的代码基础上开发的,也就是已经搭建好了Eureka注册中心和服务消费者已经配置好了Ribbon,Eureka注册中心和Ribbon客户端负载均衡这两个组件还不了解的朋友可以先去看看前面的文章《SpringCloud-01-Eureka》和《SpringCloud-02-Ribbon》。
2. 未使用Hystrix演示级联故障
2.1 模拟服务提供者业务处理异常,服务消费者级联故障
模块provider-payment9001
也就是服务提供者paymentService,在PaymentController中添加一个模拟业务处理异常的接口
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Value("${server.port}")
private String serverPort;
...
/**
* 模拟请求异常
*
* @return
*/
@GetMapping(value = "/excep")
public CommonResult<Payment> excep() {
//模拟业务处理异常
int div = 1 / 0;
return CommonResult.newInstance().setCode(200).setMessage("查询成功,serverPort:" + serverPort).setData(null);
}
...
}
模块consumer-order8001
也就是服务消费者orderService,在OrderController中添加因为一个服务请求异常,导致级联故障的接口
@RestController
@RequestMapping("/order")
public class OrderController {
public static final String PAYMENT_URL = "http://PAYMENTSERVICE";
...
/**
* 服务提供者请求异常,级联故障
*
* @return
*/
@GetMapping(value = "/excep")
public CommonResult<Payment> excep() {
return this.restTemplate.getForObject(PAYMENT_URL + "/excep", CommonResult.class);
}
...
}
分别启动服务提供者provider-payment9001
和服务消费者consumer-order8001
,在浏览器请求 http://localhost:8001/order/excep,发现出现级联故障。
服务消费者orderService出现了500 状态码。
consumer-order8001
发送网络请求后,因为provider-payment9001
业务处理异常导致consumer-order8001
的级联故障,打印的日志如下:
org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : [{"timestamp":"2021-05-18T02:40:59.394+0000","status":500,"error":"Internal Server Error","message":"/ by zero","path":"/payment/excep"}]
at org.springframework.web.client.HttpServerErrorException.create(HttpServerErrorException.java:100) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:172) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:112) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:785) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:743) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:677) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:318) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at com.xander.order8001.controller.OrderController.excep(OrderController.java:39) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.29.jar:9.0.29]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.2.RELEASE.jar:5.2.2.RELEASE] ...
2.2 模拟服务提供者业务处理响应超时,服务消费者级联故障
在模块provider-payment9001
的PaymentController中添加一个模拟业务处理响应超时的接口
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Value("${server.port}")
private String serverPort;
...
/**
* 模拟请求超时
*
* @return
* @throws InterruptedException
*/
@GetMapping(value = "/timeout")
public CommonResult<Payment> timeout() throws InterruptedException {
//模拟业务处理时间,线程睡眠3s
TimeUnit.SECONDS.sleep(3);
return CommonResult.newInstance().setCode(200).setMessage("查询成功,serverPort:" + serverPort).setData(null);
}
...
}
先看看Ribbon的源码,可以看到连接超时时间和读取超时时间都是1000ms,而在上面的PaymentController中我们设置的业务处理时间是3s(TimeUnit.SECONDS.sleep(3)),所以orderService的服务间调用会读取超时。
在consumer-order8001
的application.yml中添加Ribbon的相关配置。注意:
一定要添加 ribbon.restclient.enabled=true
这一配置,否则连接超时、读取超时、重试等这些配置都不会生效。
application.yml
### 全局Ribbon 配置
ribbon:
#启用rest client配置
restclient:
enabled: true
# http建立socket超时时间,毫秒,默认是1000
ConnectTimeout: 1000
# http读取响应socket超时时间,默认是1000毫秒
ReadTimeout: 1000
# 同一台实例最大重试次数,不包括首次调用,默认是1
MaxAutoRetries: 0
# 重试负载均衡其他的实例最大重试次数,不包括首次server
MaxAutoRetriesNextServer: 0
在 consumer-order8001
的OrderController中添加因为一个服务请求响应超时,导致级联故障的接口
@RestController
@RequestMapping("/order")
public class OrderController {
public static final String PAYMENT_URL = "http://PAYMENTSERVICE";
...
/**
* 服务提供者请求超时,级联故障
*
* @return
* @throws InterruptedException
*/
@GetMapping(value = "/timeout")
public CommonResult<Payment> timeout() {
return this.restTemplate.getForObject(PAYMENT_URL + "/timeout", CommonResult.class);
}
...
}
分别启动服务提供者provider-payment9001
和服务消费者consumer-order8001
,在浏览器请求 http://localhost:8001/order/timeout,发现出现级联故障。
服务消费者orderService出现了500 状态码。
consumer-order8001
发送网络请求后,因为provider-payment9001
业务处理超时导致consumer-order8001
的级联故障 Read timed out,打印的日志如下:
2021-05-18 15:29:12.495 ERROR 13552 --- [nio-8001-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://PAYMENTSERVICE/payment/timeout": com.netflix.client.ClientException; nested exception is java.io.IOException: com.netflix.client.ClientException] with root cause
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.8.0_191]
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) ~[na:1.8.0_191]
at java.net.SocketInputStream.read(SocketInputStream.java:171) ~[na:1.8.0_191]
...
2.3 小结
从上面两个Demo可以看到,因为服务提供者 业务处理异常 或者 业务处理超时 ,请求返回给服务消费者不符合预期或者无法处理的响应,导致了服务消费者的级联故障。
3. Hystrix服务降级
下面我们通过 Hystrix 的服务降级配置,解决因为不符合预期或者无法处理的响应,导致了服务消费者的级联故障的问题。
3.1 consumer-order8001
配置Hystrix
pom.xml中添加
<!-- netflix-hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
ConsumerOrder8001.java 添加@EnableHystrix
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix // 开启 Hystrix 功能
public class ConsumerOrder8001 {
public static void main(String[] args) {
SpringApplication.run(ConsumerOrder8001.class, args);
}
}
3.2 对异常响应进行降级处理
OrderController中新增 hystrixExcep() 接口并添加@HystrixCommand注解,配置服务降级处理方法名"fallback"
/**
* 模拟请求异常,Hystrix进行降级
*
* @return
*/
@GetMapping(value = "/hystrix/excep")
@HystrixCommand(fallbackMethod = "fallback4Excep")//配置降级处理方法
public CommonResult<Payment> hystrixExcep() {
return this.restTemplate.getForObject(PAYMENT_URL + "/payment/excep", CommonResult.class);
}
/**
* 异常降级处理方法
*
* @return
*/
public CommonResult<Payment> fallback4Excep() {
return CommonResult.newInstance().setCode(500).setMessage("客户端服务降级处理...");
}
重启consumer-order8001
,在浏览器请求 http://localhost:8001/order/hystrix/excep,可以看到响应了降级处理的结果。
3.3 对响应超时进行降级处理
从上面2.2小节的日志中,我们可以看到服务响应超时后,服务消费者这一端会抛出一个 SocketTimeoutException 的异常。
3.3.1 对响应超时产生的SocketTimeoutException进行降级处理
OrderController的 hystrixTimeout() 方法上添加@HystrixCommand注解,并配置服务降级处理方法名"fallback4Timeout"
/**
* 模拟请求超时,Hystrix进行降级
*
* @return
*/
@GetMapping(value = "/hystrix/timeout")
@HystrixCommand(fallbackMethod = "fallback4Timeout")//配置降级处理方法
public CommonResult<Payment> hystrixTimeout() {
this.logger.info("hystrixTimeout()--开始请求----"+ LocalDateTime.now());
CommonResult result = this.restTemplate.getForObject(PAYMENT_URL + "/payment/timeout", CommonResult.class);
this.logger.info("hystrixTimeout()--响应结果----"+ LocalDateTime.now());
return result;
}
/**
* 超时降级处理方法
*
* @return
*/
public CommonResult<Payment> fallback4Timeout() {
return CommonResult.newInstance().setCode(500).setMessage("对响应超时进行降级处理...");
}
重启consumer-order8001
,在浏览器请求 http://localhost:8001/order/hystrix/timeout, 可以看到响应了降级处理的结果。
3.3.2 修改orderService的超时配置,成功接收响应
先解决Ribbon服务请求超时问题,配置ribbon的读取超时时长为5s,ribbon.ReadTimeout=5000
application.yml
### 全局Ribbon 配置
ribbon:
#启用rest client配置
restclient:
enabled: true
# http建立socket超时时间,毫秒,默认是1000
ConnectTimeout: 1000
# http读取响应socket超时时间,默认是1000毫秒
ReadTimeout: 5000
# 同一台实例最大重试次数,不包括首次调用,默认是1
MaxAutoRetries: 0
# 重试负载均衡其他的实例最大重试次数,不包括首次server
MaxAutoRetriesNextServer: 0
配置方法执行超时时间,需要添加名为"execution.isolation.thread.timeoutInMilliseconds"的 @HystrixProperty。在 HystrixCommandProperties 类中,我们可以看到属性"execution.isolation.thread.timeoutInMilliseconds"的默认值是 1000,也就是说@HystrixCommand标注的方法,默认的方法执行超时时长是1000ms。
由于微服务 paymentService 中的 “/payment/timeout” 接口的业务处理时长是3s,为了不发生响应超时异常,在微服务 orderService 的 OrderController 中的"/hystrix/timeout" 接口中,添加 @HystrixProperty "execution.isolation.thread.timeoutInMilliseconds"的值为5000,也就是方法hystrixTimeout()执行超时时长是5s。
/**
* 模拟请求超时,Hystrix进行降级
*
* @return
*/
@GetMapping(value = "/hystrix/timeout")
//配置降级处理方法
@HystrixCommand(fallbackMethod = "fallback4Timeout", commandProperties = {
// 设置超时时间为5s
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")}
)
public CommonResult<Payment> hystrixTimeout() {
this.logger.info("hystrixTimeout()--开始请求----" + LocalDateTime.now());
CommonResult result = this.restTemplate.getForObject(PAYMENT_URL + "/payment/timeout", CommonResult.class);
this.logger.info("hystrixTimeout()--响应结果----" + LocalDateTime.now());
return result;
}
重启consumer-order8001
,在浏览器请求 http://localhost:8001/order/hystrix/timeout,可以看到 orderService 成功获取到 paymentService 的响应结果。
consumer-order8001
日志也可以看到,服务请求成功并且整个请求耗费了3s左右。
2021-05-18 17:26:51.674 INFO 23200 --- [derController-2] c.x.o.controller.OrderController : hystrixTimeout()--开始请求----2021-05-18T17:26:51.674
2021-05-18 17:26:54.678 INFO 23200 --- [derController-2] c.x.o.controller.OrderController : hystrixTimeout()--响应结果----2021-05-18T17:26:54.678
3.3.3 对方法执行超时进行降级处理
前面 @HystrixCommand(fallbackMethod = “xxx”) 我们都是对业务处理异常进行降级处理。如果方法执行时长超出了我们配置的超时时长后,我们也可以使用 fallbackMethod 进行降级处理。
继续 3.3.2 小节的代码,在微服务 orderService 的 OrderController 中的"/hystrix/timeout" 接口中,修改 @HystrixProperty "execution.isolation.thread.timeoutInMilliseconds"的值为2000。
/**
* 模拟请求超时,Hystrix进行降级
*
* @return
*/
@GetMapping(value = "/hystrix/timeout")
//配置降级处理方法
@HystrixCommand(fallbackMethod = "fallback4Timeout", commandProperties = {
// 设置超时时间为2s
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")}
)
public CommonResult<Payment> hystrixTimeout() {
this.logger.info("hystrixTimeout()--开始请求----" + LocalDateTime.now());
CommonResult result = this.restTemplate.getForObject(PAYMENT_URL + "/payment/timeout", CommonResult.class);
this.logger.info("hystrixTimeout()--响应结果----" + LocalDateTime.now());
return result;
}
/**
* 超时降级处理方法
*
* @return
*/
public CommonResult<Payment> fallback4Timeout() {
return CommonResult.newInstance().setCode(500).setMessage("对响应超时进行降级处理...");
}
重启consumer-order8001
,在浏览器请求 http://localhost:8001/order/hystrix/timeout,可以看到响应了超时降级处理方法的结果。
3.4 配置类级别的降级方法
上面所有的降级案例中,每个方法都配置了它们各自的降级方法,这样我们的类可能会变得越来越复杂,不方便后期的维护。
Hystrix 通过 @DefaultProperties 注解可以配置类级别的服务降级处理方法。
在consumer-order8001
的OrderController中添加 @DefaultProperties 配置,以及新增一个演示的接口"/defaultFallback"。
@RestController
@RequestMapping("/order")
@DefaultProperties(defaultFallback = "defaultFallbackMethod")//配置类级别默认的降级处理方法
public class OrderController {
......
/**
* 演示类级别默认的降级处理生效
*
* @return
*/
@GetMapping(value = "/defaultFallback")
@HystrixCommand //不配置降级方法,则使用 类级别默认的降级处理方法
public CommonResult<Payment> defaultFallback() {
return this.restTemplate.getForObject(PAYMENT_URL + "/payment/timeout", CommonResult.class);
}
/**
* 类级别默认的降级处理方法
*
* @return
*/
public CommonResult<Payment> defaultFallbackMethod() {
return CommonResult.newInstance().setCode(500).setMessage("类级别默认的降级处理方法...");
}
}
重启consumer-order8001
,在浏览器请求 http://localhost:8001/order/defaultFallback,类级别默认降级配置生效。
4. Hystrix服务熔断
前面,我们介绍了断路器工作过程, 服务熔断 指的就是断路器打开,这是一个牺牲局部,保全整体的措施。当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。
断路器工作流程如下图:
- 请求达到统计时长(metrics.rollingStats.timeInMilliseconds 统计滚动窗口的持续时间,默认是10s)
- 假设电路上的请求量达到某个阈值(circuitBreaker.requestVolumeThreshold 最小请求数,默认是20)
- 并假设误差百分比超过阈值误差百分比(circuitBreaker.errorThresholdPercentage 失败率达到多少后开启,默认是50%)
- 然后,断路器从转换CLOSED为OPEN。
- 当它断开时,它会使针对该断路器的所有请求短路,直接进行服务降级。
- 经过一段时间(circuitBreaker.sleepWindowInMilliseconds 跳闸后拒绝请求的时间,默认是5s)后,下一个单个请求被允许通过(这是HALF-OPEN状态)。
如果请求失败,继续进入步骤5,则断路器将OPEN在睡眠窗口持续时间内返回到该状态。
如果请求成功,断路器将切换到,CLOSED并且步骤1中的逻辑将再次接管。
4.1 演示服务熔断
模块provider-payment9001
也就是服务提供者paymentService,在PaymentController中添加一个模拟请求随机异常的接口
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Value("${server.port}")
private String serverPort;
...
/**
* 模拟请求随机异常
*
* @return
*/
@GetMapping(value = "/randomExcep")
public CommonResult<Payment> randomExcep() {
this.logger.info("randomExcep-----" + LocalDateTime.now());
if (Math.random() > 0.5d) {
//模拟业务处理异常
int div = 1 / 0;
}
return CommonResult.newInstance().setCode(200).setMessage("查询成功,serverPort:" + serverPort).setData(null);
}
...
}
模块consumer-order8001
也就是服务消费者orderService,在OrderController中添加一个接口调用随机异常的接口
/**
* 服务提供者请求异常,级联故障
*
* @return
*/
@GetMapping(value = "/randomExcep")
@HystrixCommand(fallbackMethod = "fallback4Excep", commandProperties = {
//是否开启断路器,默认是true
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
// 此属性设置统计滚动窗口的持续时间(以毫秒为单位)。默认是10000
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "3000"),
// 将使电路跳闸的最小请求数。例如,默认是20
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "5"),
// 设置电路跳闸后拒绝请求的时间,默认是5000
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000"),
//失败率达到多少后跳闸,默认是50
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50")
})
public CommonResult<Payment> randomExcep() {
return this.restTemplate.getForObject(PAYMENT_URL + "/payment/randomExcep", CommonResult.class);
}
重启consumer-order8001
,在浏览器请求 http://localhost:8001/order/randomExcep,可以看到当5s内,服务请求次数到达5次,并且失败率达到50%,断路器打开,服务进入熔断状态,接下来5s(sleepWindowInMilliseconds)内拒绝服务请求,直接降级处理,达到熔断时长后,放行下一个单个请求,如果请求成功则断路器关闭,如果请求失败,则继续新的熔断时长。
断路器打开后,拒绝请求,直接交给降级方法处理,如下图:
5. HystrixCommand 和 HystrixProperty
5.1 HystrixCommand注解各个参数的作用
1:groupKey:分组key,Hystrix会根据组来组织和统计命令的仪表盘等信息,默认值是使用@HystrixCommand标注的方法所在的类名。
2:commandKey:HystrixCommand的标识,默认值是@HystrixCommand标注的方法名。
3:threadPoolKey:线程池的key,Hystrix通过threadPoolKey配置线程池隔离,默认值是groupKey,而groupKey默认值是@HystrixCommand标注的方法所在类名。
4:fallbackMethod:降级处理方法,@HystrixCommand修饰的函数必须和这个回调函数定义在同一个类中,fackback method可以是public/private均可。
5:commandProperties:配置该命令的一些参数,如executionIsolationStrategy配置执行隔离策略,默认是使用线程隔离,此处我们配置为THREAD,即线程池隔离。参数请见:com.netflix.hystrix.HystrixCommandProperties中各个参数的定义。
6:threadPoolProperties:线程池相关参数设置,具体可以设置哪些参数请见:com.netflix.hystrix.HystrixThreadPoolProperties
7:ignoreExceptions:定义应忽略的异常,通过这个参数指定,哪些异常抛出时不触发降级(不去调用fallbackMethod),而是将异常向上抛出。
8:observableExecutionMode:指定应用于执行hystrix observable命令的模式。;
9:raiseHystrixExceptions:任何不可忽略的异常都包含在HystrixRuntimeException中;
10:defaultFallback:默认的回调函数,没有入参,返回值类型与@HystrixCommand修饰的函数体的返回值一致。如果指定了fallbackMethod,则fallbackMethod优先级更高。
5.2 HystrixCommandProperties 熔断配置
请参考 com.netflix.hystrix.HystrixCommandProperties。
- circuitBreaker.enabled:是否开启断路器功能,默认是true
- circuitBreaker.requestVolumeThreshold:设置在滚动时间窗口中(一段时间内),断路器的最小请求数。默认20,如果在窗口时间内请求次数19,即使19个全部失败,断路器也不会打开。
- circuitBreaker.sleepWindowInMilliseconds:设置当断路器打开之后的休眠时间,默认5000。
- circuitBreaker.errorThresholdPercentage:设置断路器打开的错误百分比,默认50%。
- circuitBreaker.forceOpen:true表示强制打开断路器,拒绝所有请求
- circuitBreaker.forceClosed:true表示强制进入关闭状态,接收所有请求
- metrics.rollingStats.timeInMilliseconds:设置滚动时间窗的长度,单位是毫秒,默认10000。这个时间窗口就是断路器收集信息的持续时间。
- metrics.rollingStats.numBuckets:滚动时间窗统计指标信息划分的桶的数量,但是滚动时间必须能够整除这个桶的个数,要不然抛异常。
5.3 HystrixCommandProperties 线程隔离(Isolation)
- execution.isolation.strategy: 配置请求隔离的方式,有 threadPool(线程池,默认)和 semaphore(信号量)两种,信号量方式高效但配置不灵活,我们一般采用 Java 里常用的线程池方式。
- execution.timeout.enabled:是否给方法执行设置超时,默认为 true。
- execution.isolation.thread.timeoutInMilliseconds:方法执行超时时间,默认值是 1000,即 1秒,此值根据业务场景配置。
- execution.isolation.thread.interruptOnTimeout: execution.isolation.thread.interruptOnCancel:是否在方法执行超时/被取消时中断方法。需要注意在 JVM 中我们无法强制中断一个线程,如果 Hystrix 方法里没有处理中断信号的逻辑,那么中断会被忽略。
- execution.isolation.semaphore.maxConcurrentRequests:默认值是 10,此配置项要在 execution.isolation.strategy 配置为 semaphore 时才会生效,它指定了一个 Hystrix 方法使用信号量隔离时的最大并发数,超过此并发数的请求会被拒绝。信号量隔离的配置就这么一个,也是前文说信号量隔离配置不灵活的原因。
5.3 HystrixThreadPoolProperties 线程池配置
请参考 com.netflix.hystrix.HystrixThreadPoolProperties。
- allowMaximumSizeToDivergeFromCoreSize:是否允许线程池扩展到最大线程池数量,默认为 false;HystrixPropertiesManager中不存在该属性,不能通过 HystrixProperty 进行配置。
- coreSize:核心线程数,默认是10;
- maximumSize:最大线程数,默认是10;只有allowMaximumSizeToDivergeFromCoreSize=true才生效;HystrixPropertiesManager中不存在该属性,不能通过 HystrixProperty 进行配置。
- keepAliveTimeMinutes:线程被回收前端的空闲时间,默认是1,也就是一分钟;
- maxQueueSize:队列长度,默认是-1,表示使用的是SynchronousQueue,不存储任务;
- queueSizeRejectionThreshold:队列大小阈值,超过则拒绝,默认是5,;
注意:
HystrixCommandProperties和HystrixThreadPoolProperties中所有的属性,必须在HystrixPropertiesManager存在的静态属性才能进行配置,否则抛出异常unknown thread pool property:xxx。
举例:比如线程池的最大线程数 maximumSize 不能通过 HystrixProperty 进行配置,如果进行配置,则会抛出异常unknown thread pool property:maximumSize。
HystrixPropertiesManager.java
6. 线程池隔离
前面介绍,threadPoolKey用于初始化创建线程池,如果不指定 threadPoolKey 和 groupKey,threadPoolKey的默认值是groupKey,而groupKey默认值是@HystrixCommand标注的方法所在类名,因此默认所有在一个类中的HystrixCommand共用一个线程池。
如果不通过 threadPoolKey 给 HystrixCommand 创建线程池,那么同一个类中共用一个线程池,如果某个HystrixCommand调用了某个表现不佳的服务,导致线程池中的线程被该HystrixCommand所占用,如果线程池所有线程都在工作,但是队列未满也未达到拒绝阈值,则其他的HystrixCommand的请求会放到任务队列中等待执行,也就是会阻塞HystrixCommand的处理,如果队列满后或者到达队列拒绝阈值后,请求会直接进行降级处理。
线程池配置举例
/**
* 模拟线程池隔离,当线程池满负荷后,直接进行降级处理,达到限流的效果
*
* @return
*/
@GetMapping(value = "/hystrix/threadpool")
//配置降级处理方法
@HystrixCommand(fallbackMethod = "defaultFallbackMethod", threadPoolKey = "OrderController", commandProperties = {
// 设置超时时间为5s
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"),
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD")
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "1"),
@HystrixProperty(name = "maxQueueSize", value = "-1")
}
)
public CommonResult<Payment> hystrixThreadpool() {
this.logger.info("hystrixThreadpool()--开始请求----" + LocalDateTime.now() + "---" + Thread.currentThread().getName() + "---" + Thread.currentThread().getId());
CommonResult result = this.restTemplate.getForObject(PAYMENT_URL + "/payment/timeout", CommonResult.class);
this.logger.info("hystrixThreadpool()--响应结果----" + LocalDateTime.now());
return result;
}