技术需求点:
在SpringCloud+SpringBoot部署的微服务系统中,模拟服务故障,证明Hystrix组件可以保证当服务提供者提供的服务不可用时,调用方可以切换到降级后的执行策略,保持系统服务稳定性,同时介绍Hystrix的算法机制和原理。
Hystrix是一种叫豪猪的动物,身体后半部分长满了圆棘刺,当遇到危险时,通过后退的攻击方式将棘刺插入敌人身体,以保护自己不受伤害。
前言:分布式系统服务雪崩问题
在分布式系统中,多个服务之间通常是通过Rpc远程调用的方式实现的,这种服务依赖的模式可能会带来问题,如果被依赖的服务A挂掉,而调用者B没采取任何防御措施,可能会导致调用者B的所有线程在数秒内处于阻塞状态释放不了,后续请求累积增加,可能导致调用者B瘫痪,服务B瘫痪又会导致B的调用者C瘫痪......最后引起服务雪崩问题。因此,一定的防御措施(降级策略)需要在系统设计时考虑到,当服务调用方发现提供方的服务出现异常时,调用方能够快速切换到预置好的降级策略中。
正常时,调用者(UserRequest)调用Dependency A、H、I、P服务:
当Dependcency I系统出现问题,调用者UserRequest也会阻塞:
在请求量较大时,在数秒内所有线程会阻塞,导致所有资源饱和,系统无法再提供服务。
一.Hystrix基于自反馈调节熔断状态的算法原理
在电路系统中,会有保险丝,当电流过大产热过大时,保险丝会熔断以切断与外部电路的联通。Hystrix充当了保险丝的角色,当请求量过大且请求错误量超过我们设定的阈值时,Hystrix的熔断器会熔断,让调用者服务自动降级以保护服务。自动降级后的服务不会再远程调用依赖方的服务,转向我们的降级策略去执行,比如给个失败页面快速失败,减少调用方的漫长等待。
此外,熔断开启,服务降级后,Hystrix有自动恢复的功能,假如我们设定每次熔断后休眠5秒钟(5秒钟内不调用远程服务),到时间后会自动重试,让20次请求尝试调用远程服务,如果能返回正常结果,就认为服务恢复正常,熔断器自动关闭,服务降级停止。
根据这一过程,Hystrix的状态机分为三种状态:open、closed、half-open,熔断器会在这三种状态之间切换。具体来讲:
- open:被调用方在一定时间窗内请求错误率大于设定的阈值,被判定为异常,熔断开启,服务降级,调用方执行本地的降级策略,不进行远程调用;
- closed:被调用服务正常,熔断关闭,调用方远程调用服务。
- half-open:尝试的中间状态,调用方熔断且在休眠了一段时间后,调用方发起测试性的远程调用,测试被调用者是否正常。
熔断器的开关状态取决于HystrixCommandMetrics对象里面的数据,该对象存储了类似远程接口调用次数,失败次数等数据,以时间窗口的形式统计各维度数据。
二.模拟Hystrix实战
1.pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.开启Hystrix
在EurekaApplication文件上添加注解@EnableCircuitBreaker开启熔断
@EnableCircuitBreaker//开启熔断
@EnableEurekaClient//开启Eureka客户端
@SpringBootApplication
@EnableFeignClients//开启Feign客户端
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
添加之后,感觉注解越来越多,因此可以合并下。
首先将@EnableEurekaClient替换成@EnableDiscoveryClient,两者的共同点是:都能让注册中心发现自己的服务,区别是@EnableEurekaClient只支持Eureka作为注册中心,而@EnableDiscoveryClient支持其它注册中心,比如consul。
然后将@EnableCircuitBreaker、@EnableEurekaClient、@SpringBootApplication替换成@SpringCloudApplication,看@SpringCloudApplication的代码可以看出,该注解就是集成了前面三个。
3.调用者接口
对getUsers()方法进行熔断,需要给这个方法写一个对应的fallback方法,当调用提供者服务出现异常时,Hystrix会自动执行fallback的方法。当然,需要给方法指定熔断方法,用@HystrixCommand注解声明,
(如果想给类的所有方法都指定一个熔断方法,使用@DefaultProperties(defaultFallback="getUsersFallback"),需要熔断的方法再加@HystrixCommand才行)。注解里面还可以进行各种Hystrix的配置,例如熔断时间等等,下面再说。
@RestController
public class UserController {
@Autowired
private UserFeignService userFeignService;
@RequestMapping("/consumer")
@HystrixCommand(fallbackMethod = "getUsersFallback")
public String getUsers(){
return userFeignService.getUsers();
}
public String getUsersFallback(){
return "调用的服务出现异常了";
}
}
其中,UserFeignService的代码如下:
//开启feign客户端,eureka-provider-user微服务的serviceid(唯一)
@FeignClient("eureka-provider")
public interface UserFeignService {
@RequestMapping("/user")
public String getUsers();
}
4.被调用者接口
@RestController
public class UserController {
@RequestMapping("/user")
public String getUsers(){
return "provider的User服务";
}
}
完整的基于Eureka注册中心的微服务搭建过程,请参考SpringCloud+SpringBoot搭建服务注册与调用平台这篇文章。
5.测试结果
当consumer能正常调用provider服务时,
此时,停掉provider服务,再次调用:
说明熔断成功,重启provider服务,又可以正常调用了。
6.将熔断放在service层级
上面的熔断方法我们是放在controller层级的,但这给业务带来了极大的耦合,我们希望把service层就提供了熔断服务,controller层只调用即可,出现异常在service层处理掉。我们使用feign组件发起远程调用, 因此,在使用feign组件基础上,我们这样优化:
(1)先创建一个实现类UserFeignFallback implements UserFeignService,
@Component
public class UserFeignFallback implements UserFeignService {
@Override
public String getUsers() {
return "Service层:调用的服务出现异常了";
}
}
(2)在UserFeignService指定fallback的方法:
@FeignClient(value="eureka-provider",fallback = UserFeignFallback.class)
public interface UserFeignService {
@RequestMapping("/user")
public String getUsers();
}
(3)在配置文件中开启feign的hystrix熔断功能:
feign.hystrix.enabled=true
(4)最后把controller的接口改成普通的调用接口即可:
@RequestMapping("/consumer")
public String getUsers(){
return userFeignService.getUsers();
}
三.什么情况下会触发fallback方法?
(引自参考4)
事件 | 描述 | 触发fallback |
---|---|---|
EMIT | 值传递 | NO |
SUCCESS | 执行完成,没有错误 | NO |
FAILURE | 执行抛出异常 | YES |
TIMEOUT | 执行开始,但没有在允许的时间内完成 | YES |
BAD_REQUEST | 执行抛出HystrixBadRequestException | NO |
SHORT_CIRCUITED | 断路器打开,不尝试执行 | YES |
THREAD_POOL_REJECTED | 线程池拒绝,不尝试执行 | YES |
SEMAPHORE_REJECTED | 信号量拒绝,不尝试执行 | YES |
四.Hystrix可以配置哪些参数
配置方式有2种,一个是在注解@HystrixCommand属性里面配置(比较麻烦),一个是在配置文件中配置,两种都可以,但在注解中配置的优先级更高,下面是一些常用的配置
超时时间(默认1000ms,单位:ms)
1.hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
调用者的配置文件中配置,调用者所有方法的超时时间都是该值
2.hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds
调用者的指定方法中配置,优先级高于上边的全局配置统计次数
circuitBreaker.requestVolumeThreshold
当在配置时间窗口内达到此数量的失败后,进行熔断,默认20个close休眠试间
circuitBreaker.sleepWindowInMilliseconds
熔断多久后开始尝试是否恢复,默认5s出错百分比,
circuitBreaker.errorThresholdPercentage
当错误超过该百分比触发熔断,默认50%线程池核心线程数
hystrix.threadpool.default.coreSize(默认为10)
后记:Hystrix现状-官方社区已死
Hystrix官网已经不再维护新版本,但是因为其版本较为稳定,现阶段不影响用户对它的正常使用。Hystrix的替代方案有两个:
1.Alibaba Sentinel
Sentinel在阿里内部已经被大规模采用,非常稳定,目前在Spring Cloud的孵化器项目Spring Cloud Alibaba中,是阿里开源的一款断路器实现。
2.Resilience4J
Resilience4J是Hystrix官方推荐的替代方案,是一款轻量级框架,使用简单,并且文档非常清晰、丰富。
Tips:现在我们再来看豪猪这种动物,是不是跟hystrix的机制原理一模一样,当发现各种服务故障(敌人)时,调用fallback方法(倒退)快速失败,提高系统抵御灾害能力,作者不但写出了这么强大的组件,还命名了这么贴切的名字,膜拜!
参考文章:
参考1,原理简介:https://www.jianshu.com/p/b8d3eb0cf721?utm_source=oschina-app
参考2,源码解析:https://www.jianshu.com/p/684b04b6c454
参考3,Hystrix介绍:https://www.cnblogs.com/cjsblog/p/9391819.html
参考4,详细属性配置:https://blog.csdn.net/tongtong_use/article/details/78611225