1、前言
在微服务架构体系下,服务间不可避免地会发生依赖关系,并且一般都会通过REST AP来进行通信。在理想情况下,什么问题都不发生当然是最好的。但服务运行期间难免会出现各种问题,如网络阻塞,延迟过高,甚至服务直接挂掉等情况都是很有可能发生的。如果服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,直到提供者响应或超时。在高负载的情况下,如果不做任何处理的话,此类问题可能会导致服务消费者的资源耗竭甚至整个系统的奔溃。我们把这种因提供者的不可用从而导致消费者不可用,并将不可用逐渐放大的现象称为雪崩效应。要想防止雪崩效应,必须有一个强大的容错机制。该机制需实现以下两点:(1)为网路请求设置超时;(2)使用断路器模式。Hystrix就是一个实现超时机制与断路器模式的工具类库。
2、Hystrix简介
Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性和容错性,Hystrix主要通过以下几点实现延迟和容错:
- 包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。
- 跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以手动或自动跳闸,停止请求该服务一段时间。
- 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是等候,从而加速失败判定。
- 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功,失败、超时,以及被拒绝的请求等。
- 回退机制:当请求失败、超时、被拒绝,或者断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供。
- 自我修复:断路器打开一段时间后,会自动进入一种“半开”状态。此时,断路器可允许一个请求访问依赖的服务。如果该请求能够调用成功,则关闭断路器;否则继续保持打开状态。
3、Hystrix的工作流程
下图来自Hystrix官网, 详细描述了Hystrix的工作流程
图片中序号解释如下:
1、构造一个 HystrixCommand或HystrixObservableCommand对象, 用于封装请求, 并在构造方法配置请求被执行需要的参数;
2、执行命令, Hystrix提供了4种执行命令的方法, execute和queue 适用于HystrixCommand对象, 而observe和toObservable适用于HystrixObservableCommand对象;
3、判断是否使用缓存响应请求, 若启用了缓存, 且缓存可用, 直接使用缓存响应请求. Hystrix支持请求缓存, 但需要用户自定义启动;
4、判断熔断器是否打开, 如果打开, 跳到第8步;
5、判断线程池/队列/信号量是否已满, 已满则跳到第8步;
6、执行HystrixObservableCommand.construct或HystrixCommand.run, 如果执行失败或者超时, 跳到第8步; 否则, 跳到第9步;
7、统计熔断器监控指标;
8、走Fallback备用逻辑;
9、返回请求响应.
4、Hystrix功能实现
Hystrix实际应用场景很多,比如隔离、限流、降级、熔断、监控等,以下只做了降级、熔断的功能实现。
首先构建一个项目环境,包含如下几个项目,测试均为spring boot单体项目
Eureka注册中心:demo-spring-eureka
提供者:demo-spring-provider
消费者:demo-spring-consumer
4.1、服务降级
4.1.1 从消费者端进行服务降级
demo-spring-consumer使用feign做服务间的调用,引入feign依赖,因为Feign中已经包含Hystrix功能,故不重复引入hystrix包
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> |
在介绍hystrix具体功能实现之前,我们先来了解下@FeignClient注解标签的常用属性:
- name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
- url: url一般用于调试,可以手动指定@FeignClient调用的地址
- decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
- configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
- fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
- fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
- path: 定义当前FeignClient的统一前缀
方式1:修改Feign接口,使用@ FeignClient注解的fallback属性为指定名称的Feign客户端添加回退
@FeignClient(name = "demo-spring-provider",fallback = TestApiFeignFallback.class) public interface TestApiFeign { /** * 一个测试feign方法 * @return */ @RequestMapping(value = "/info",method = RequestMethod.GET) String testA(); } @Component public class TestApiFeignFallback implements TestApiFeign { @Override public String testA() { return "调用失败"; } } |
方式2:修改Feign接口,使用@ FeignClient注解的fallbackFactory属性为指定名称的Feign客户端添加回退
@FeignClient(name = "demo-spring-provider",fallbackFactory = TestApiFeignFactory.class) public interface TestApiFeign { /** * 一个测试feign方法 * @return */ @RequestMapping(value = "/info",method = RequestMethod.GET) String testA(); } @Component @Slf4j public class TestApiFeignFactory implements FallbackFactory<TestApiFeign> { @Override public TestApiFeign create(Throwable cause) { return new TestApiFeign() { @Override public String testA() { log.info("falback cause was:", cause); return cause.getMessage(); } }; } } |
以上两种方式都能实现服务降级,fallback不能捕获异常打印堆栈信息,不利于问题排查,因此不推荐。fallbackFactory可以捕获异常信息并返回默认降级结果,可以打印堆栈信息,推荐使用。
4.1.2 从提供者端进行服务降级
首先在提供者端加入Hystrix的依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> |
demo-spring-provider服务中改造feign接口方法实现,添加@HystrixCommand注解。
@RequestMapping(value = "/info", method = RequestMethod.GET) @HystrixCommand(fallbackMethod = "TimeOutErrorHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") }) public String testA() { try { //方法休眠5秒,一定会发生错误,也就会调用下边的fallbakcMethod方法 TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } return "服务正常调用"; } /** * 这个就是当上边方法的“兜底”方法 */ public String TimeOutErrorHandler() { return "对不起,系统处理超时"; } |
@HystrixCommand注解要注意三点:
- FallCallbackMethod中的这个参数就是“兜底”方法,错误回退时触发
- fallCallbackMethod中的这个方法的声明要和实际方法名一致
- commmandProperties属性中可以写多个@HystrixProperty注解,其中的name和value就是配置对应的属性,上例中的这个就是配置响应超时
最后,在启动类上加上@EnableCircuitBreaker注解
@EnableEurekaClient @SpringBootApplication @EnableCircuitBreaker public class DemoSpringProviderApplication { public static void main(String[] args) { SpringApplication.run(DemoSpringProviderApplication.class, args); } } |
以上示例理论上是要返回“服务调用正常”,但是呢,由于我们人为造成了超时错误,所以就一定会返回fallback中的“对不起,系统处理超时”,而且这个返回是会在2秒后。
4.2 服务熔断
当启用服务降级时,会默认启用服务熔断机制,我们只需要对一些参数进行配置就可以了,就是在上边的@HystrixCommand中的一些属性,比如:
@HystrixCommand(fallbackMethod = "TimeOutErrorHandler",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "2000"), @HystrixProperty(name="circuitBreaker.enabled",value="true"),//开启断路器 @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value="20"),//请求次数的峰值 @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="10000"),//检测错误次数的时间范围 @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="60")//请求失败率达到多少比例后会打开断路器}) |
更多配置可以参考 https://github.com/Netflix/Hystrix/wiki/Configuration
服务熔断,就好比我们家中的保险丝。当检测到家中的用电负荷过大时,就断开一些用电器,来保证其他的可用。在分布式系统中,就是调用一个系统时,在一定时间内,这个服务发生的错误次数达到一定的值时, 我们就打开这个断路器,不让调用过去,而是让他直接去调用降级方法。再过一段时间后,当一次调用时,发现这个服务通了,就将这个断路器改为“半开”状态,让调用一个一个的慢慢过去,如果一直没有发生错误,就将这个断路器关闭,让所有的服务全部通过。
值得注意的是:执行回退逻辑并不代表断路器已经打开,请求失败、超时、被拒绝以及断路器打开时都会执行回退逻辑。只有当请求的失败率达到阈值(默认是5秒内20次失败),断路器才会打开。