服务雪崩:
如果扇出的链路上某个微服务的响应超时或者不可用,对微服务调用方的调用会占用越来越多的系统资源,进而引起系统崩溃,这就是"雪崩效应"。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和,比失败更糟糕的是,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不会导致整个应用程序或系统崩溃。
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