什么是Hystrix?
在分布式系统中,服务与服务之间的依赖错综复杂,一种不可避免的情况就是某些服务会出现故障,导致依赖于它们的其他服务出现远程调度的线程阻塞。Hystrix提供了熔断器功能,能够阻止分布式系统中出现联动故障,通过隔离服务的访问点阻止联动故障的,并提供了故障的解决方案,从而提高 了整个分布式系统的弹性。
简单来说:微服务架构中存在很多的服务,例如A、B、C、D、E5个服务,并且对于一次调用来说,并不仅仅是访问一个服务,按照功能划分来说,每个服务做得事情是单一的,所以大部分的请求都是类似于A-C-E,跨过多个服务。
如果单个服务的正常运行几率为99.00%(机房的不可靠性、运营商的不可靠性等等),那么对于A-C-E调用流程来说,几率为99.00%*99.00%*99.00%=97.03%,也就意味着10000次请求有300多次会失败,问题还是比较严重的。
Hystrix设计原则
- 防止单个服务的故障耗尽整个服务的Servlet容器(例如Tomcat)的线程资源。
- 快速失败机制,如果某个服务出现了故障,则调用该服务的请求快速失败,而不是线程等待。
- 提供回退(fallback)方案,在请求发生故障时,提供设定好的回退方案。
- 使用熔断机制,防止故障扩散到其他服务。
- 提供熔断器的监控组件Hystrix Dashboard ,可以实时监控熔断器的状态。
Hystrix工作机制
- 当服务的某个 API 接口的失败次数在一定时间内小于设定的阀值时,熔断器处于关闭状态,该 API 接口正常提供服务;
- 当该API 接口处理请求的失败次数大于设定的阀值时, Hystrix 判定该 API 接口出现了故障,打开熔断器,这时请求该 API接口会执行快速失败的逻辑(即 fall back 回退的逻辑),不执行业务逻辑,请求的线程不会处于阻塞状态;
- 处于打开状态的熔断器一段时间后会处于半打开状态,并将一定数量的请求执行正常逻辑。剩余的请求会执行快速失败,若执行正常逻辑的请求失败了,则熔断器继续打开;若成功了 ,则将熔断器关闭。这样熔断器就具有了自我修复的能力。
使用方法
1、在RestTemplate和Ribbon上使用熔断器
找到本人之前的eureka-consumer项目,gitbub地址:https://github.com/ali-mayun/eureka-consumer
新加依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.1.1.RELEASE</version> </dependency>
运行主类:
package com.ty.eurekaconsumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.hystrix.EnableHystrix; @EnableEurekaClient @SpringBootApplication //使用这注解开启hystrix的功能 @EnableHystrix public class EurekaConsumerApplication { public static void main(String[] args) { SpringApplication.run(EurekaConsumerApplication.class, args); } }
更改之前的service
package com.ty.eurekaconsumer.service; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.client.RestTemplate; @Service public class RibbonService { @Autowired private RestTemplate restTemplate; //使用@HystrixCommand注解启用熔断器的功能,当服务熔断后,将不调用远程服务,直接调用本地的handleError方法 @HystrixCommand(fallbackMethod = "handleError") public String hiService(String name) { return restTemplate.getForObject("http://eureka-provider/firstCall?name=" + name, String.class); } public String handleError(String name) { return name + "在访问远程服务时,被熔断了,调用本地的方法"; } }
现在启动eureka-server、eureka-consumer两个项目,不启动eureka-provider,制造服务不能用的假象,运行效果如下:
2、在feign上使用熔断器
上面一种方式相对来说还是比较麻烦的,使用feign则方便很多。
打开上篇博文中的eureka-feign-consumer项目,github地址:https://github.com/ali-mayun/eureka-feign-consumer
修改application.properties文件,开启Hystrix功能:
# 服务名称 spring.application.name=eureka-feign-consumer # 端口号 server.port=4001 # 服务注册中心地址 eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/ # 增加Hystrix功能 feign.hystrix.enabled=true
更改EurekaClientFeign:
package com.ty.eurekafeignconsumer.service; import com.ty.eurekafeignconsumer.config.FeignConfig; import com.ty.eurekafeignconsumer.hystrix.BaseHystrix; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; //新增fallback属性指定出现熔断由哪个类来处理。 @FeignClient(value = "eureka-provider", configuration = FeignConfig.class, fallback = BaseHystrix.class) public interface EurekaClientFeign { //只需要在接口中定义方法即可。调用eureka-provider服务的firstCall方法,并且feign集成了Ribbon @GetMapping(value = "/firstCall") String sayHiFromClientEureka(@RequestParam("name") String name); }
BaseHystrix:
package com.ty.eurekafeignconsumer.hystrix; import com.ty.eurekafeignconsumer.service.EurekaClientFeign; import org.springframework.stereotype.Component; //熔断处理类需要实现调用远程服务的service,例如本案例在EurekaClientFeign类中调用远程服务,就需要实现此接口, //然后相同名字的方法就可以一一对应 @Component(value = "baseHystrix") public class BaseHystrix implements EurekaClientFeign { @Override public String sayHiFromClientEureka(String name) { return name + "访问的远程服务被熔断"; } }
controller中有个坑,就是EurekaClientFeign是一个接口,主类中使用@EnableFeignClients开启了feign功能,该注解会扫描@FeignClient,将使用到该注解的接口通过动态代理的方式创建类,因此EurekaClientFeign接口就会有两个实现类。
另一个是BaseHystrix,用来处理发生熔断后的逻辑,因此这里使用@Autowired就出错了,但是使用@Resource就ok,但是暂不清楚为啥会正确的找到动态代理生成的那个类,后续研究。。。
package com.ty.eurekafeignconsumer.controller; import com.ty.eurekafeignconsumer.service.EurekaClientFeign; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController public class FeignController { //这个地方有点坑,因为EurekaClientFeign已经有另外一个实现类就是BaseHystrix,使用@Autowired会报错 //不过暂时不清楚@Resource会注入正确的bean,后续待研究。。。 @Resource private EurekaClientFeign eurekaClientFeign; @GetMapping("/hi") public String sayHi(@RequestParam(value = "name", defaultValue = "马云", required = false) String name) { return eurekaClientFeign.sayHiFromClientEureka(name); } }
当eureka-provider正常开启,效果如下:
关闭eureka-provider,访问效果如下: