系统负载过高,突发流量或网络等各种异常情况,常用解决方案如下:
- 限流:对访问量进行限制,防止流量太大直接使整个服务发生故障;
- 熔断:为了防止整个系统故障,停止出现问题的服务的访问;
- 降级:抛弃一些非核心的接口和数据;
- 熔断和降级互相交集:
- 相同点:从可用性和可靠信息出发,为防止系统崩溃;最终让用户体验到的是某些功能暂时不可用
- 不同点:服务熔断一般是下游服务故障导致,而服务降级一般是从整个系统负荷考虑,由调用方控制
Hystrix的简介
Netflix的Hystrix(豪猪)通过对调用的服务进行资源隔离,实现熔断降级的功能,提升分布式系统的可用性和稳定性。
设计原则
hystrix为了实现高可用性的架构,设计hystrix的时候,遵循了以下这些设计原则:
- 对依赖服务调用时出现的调用延迟和调用失败进行控制和容错保护;
- 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延;如有如下调用链:
服务A->服务B->服务C
,当服务C发生故障时,如果没做处理那么服务B和服务A都会受到影响发生故障,导致整套分布式系统全部故障、整体宕机。 - 提供fail-fast(快速失败)和快速恢复的支持;
- 提供fallback优雅降级的支持;
- 支持近实时的监控、报警以及运维操作;
具体实现细节:
- 阻止任何一个依赖服务耗尽所有的资源,比如tomcat中的所有线程资源
- 避免请求排队和积压,采用限流和fail fast来控制故障
- 提供fallback降级机制来应对故障
- 使用资源隔离技术,比如Bulkhead(舱壁隔离技术),Swimlane(泳道技术),circuit breaker(短路技术),来限制任何一个依赖服务的故障的影响
- 通过近实时的统计/监控/报警功能,来提高故障发现的速度
- 通过近实时的属性和配置热修改功能,来提高故障处理和恢复的速度
- 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况
实现的方案的简述
- Hystrix通过外部依赖的访问请求进行封装,让其运行在独立的线程中达到资源隔离的目的;
- 对请求的处理时间进行监控,如果超过设置的阀值那么会直接让其超时返回,不允许其耗费过长的时间阻塞住;
- 为每个依赖服务维护一个独立的线程池,当线程池满了的时候就会直接拒绝这个服务的调用;
- 统计依赖服务的调用情况,比如成功数、失败数、拒绝次数、超时次数等;
- 根据依赖服务的调用情况,自动判断其健康状态,如果失败次数超过阀值自动进行熔断(在一定时间内直接对该服务进行降级(也就是在调用的时候直接返回失败),一段时间后再自动恢复尝试);
- 当一个服务调用出现失败,被拒绝,超时,短路等异常情况时,自动调用fallback降级机制;
- 对属性和配置的修改提供近实时的支持;
这里顺便提一下:feign中它自己其实也有fallback降级机制(只是功能没有那么强大),比如如果依赖服务发生故障时虽然有fallback降级机制,但是还是会将请求向该服务发送(该等待的时间依然会等待);
当我们引入Hystrix并启用后,feign中的fallback降级机制将交给Hystrix来实现,而Hystrix会在监控到服务调用发生故障时,它会判断其失败次数,如果超过阀值,在一段时间内其它服务调用它的时候会直接返回降级处理策略,不会再向该服务发送请求了。
线程池隔离技术与信号量隔离技术的区别
hystrix里面最核心的一项功能就是资源隔离,它想要解决的问题就是:避免因为一个依赖服务的调用发生延迟、失败或阻塞的文档,导致系统所有的线程资源都全部耗费在这一个依赖服务的接口调用上,因此hystrix将依赖的多个服务的调用分别隔离到各自的资源池内,这样一旦某个服务的线程资源耗尽,那么只会会影响那一个接口调用,而不会导致其故障蔓延到整个服务,最终整个服务都崩溃了。
由此hystrix的资源隔离分为两种技术:线程池的资源隔离和信号量的资源隔离;
- 线程池的资源隔离:就是给每个依赖服务的接口单独一个线程池,达到上限就进入队列排队,线程池中配置可以使用的线程数量,一旦超过阀值就直接拒绝请求;这是默认的方式,可以避免在网络超时的时候导致线程阻塞等待。
- 信号量的资源隔离:就是对每个依赖服务的接口访问量设置一个阀值,达到上限就进入队列排队,一旦超过阀值就直接拒绝请求;这种通常都是对内存中的数据进行操作,或者是应对海量的访问时进行限流。
Hystrix 使用示例
下面我们通过一些示例让你快速了解它的基本使用。示例使用的Spring-Cloud的版本是Hoxton.SR8,Spring-Boot的版本是2.3.4.RELEASE。示例项目的源代码
相关依赖和配置
添加如下maven的依赖(注意feign的请自行添加,feign的使用参考: https://blog.vchar.top/java/1621167133.html )
<!-- hystrix的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
然后在启动类上添加如下注解:@EnableCircuitBreaker
或者是 @EnableHystrix
; 效果都是一样的(@EnableHystrix继承了@EnableCircuitBreaker注解)。
单独使用 hystrix
通过添加@HystrixCommand注解可以让该方法被hystrix监控到实现熔断降级策略。
示例1:在controller中直接添加熔断降级处理方式
@HystrixCommand(fallbackMethod = "hystrixMethodDemoFail")
@GetMapping("/demo1")
public Train findTrain(){
System.out.println("执行...");
// 这里发送异常时会自动执行hystrixMethodDemoFail方法
return this.hystrixDemoService.hystrixMethodDemo();
}
/**
* 这里一定要和HystrixCommand注解中的方法一致,且参数也必须一致;当服务异常时会调用此方法
*/
private Train hystrixMethodDemoFail(){
System.out.println("调用异常");
return null;
}
示例2:在service中直接添加熔断降级处理方式
@HystrixCommand(fallbackMethod = "hystrixMethodDemo2Fail")
@Override
public Train hystrixMethodDemo2() {
System.out.println("hystrixMethodDemo2 调用feign服务:trainFeignClient.findTrain()");
return this.trainFeignClient.findTrain();
}
private Train hystrixMethodDemo2Fail(){
System.out.println("hystrixMethodDemo2Fail ...");
return null;
}
示例3:方法间的调用
@Override
public Train hystrixMethodDemo3() {
// hystrixMethodDemo2做了熔断降级的
return this.hystrixMethodDemo2();
}
因为hystrix是通过AOP来实现的;因此一个类的方法间调用时不会生效的。
结合feign使用hystrix
feign之前的写法不用改变,继续保持即可;只需要添加如下配置就可以将feign的熔断降级处理的方式切换到hystrix:
feign:
hystrix:
# 启用feign中的hystrix
enabled: true
hystrix的监控界面
添加下面的maven依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在启动类上添加注解: @EnableHystrixDashboard
;然后添加如下配置:
management:
endpoints:
web:
exposure:
include: "*"
hystrix:
dashboard:
# 配置允许的域名
proxyStreamAllowList:
- "localhost"
然后访问监控界面:http://localhost:8100/hystrix ;在里面输入 http://localhost:8100/actuator/hystrix.stream
点击 Monitor Stream进入监控界面。
线程池资源隔离示例
资源隔离简单来说就是对某个依赖服务的请求,全部固定使用一个线程池中的资源,当资源不足时则排队等待或直接拒绝请求。这样可以减少网络超时、流量激增时将依赖服务干掉的风险。默认配置就是线程资源隔离