服务雪崩
假设存在如下调用链:
而此时,Service A的流量波动很大,流量经常会突然性增加!那么在这种情况下,就算Service A能扛得住请求,Service B和Service C未必能扛得住这突发的请求。
此时,如果Service C因为抗不住请求,变得不可用。那么Service B的请求也会阻塞,慢慢耗尽Service B的线程资源,Service B就会变得不可用。紧接着,Service A也会不可用,这一过程如下图所示
如上图所示,一个服务失败,导致整条链路的服务都失败的情形,我们称之为服务雪崩。
那么,服务熔断和服务降级就可以视为解决服务雪崩的手段之一。
服务熔断
当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。Hystrix默认的超时时间为1s,请求时间超过1s,熔断次数加1。
熔断器机制:
- closed:请求正常时,不使用熔断器;
- open:统计请求的失败比例,达到阀值时,打开熔断器,请求被降级处理;延时一段时候后(默认休眠时间是5S)会进入halfopen状态;默认失败比例阀值是50%,请求次数最少不低于20次,例如:一段时间内请求30次,有20失败,熔断器将会打开,默认5s内的请求都会失败,直接返回。
- halfopen:在进入该状态后会放入部分请求;判断请求是否成功,不成功,进入open状态,重新计时;成功,进入closed状态
服务降级
服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。
服务熔断和服务降级的异同
相同点:
- 目标一致 都是从可用性和可靠性出发,为了防止系统崩溃;
- 用户体验类似 最终都让用户体验到的是某些功能暂时不可用;
不同点: - 触发原因不同 服务熔断一般是某个服务(下游服务)故障引起
- 而服务降级一般是从整体负荷考虑;
Hystrix
Hystris 是一个处理分布式系统的 延迟 和 容错 的开源库,在服务调用中不可避免的会调用失败,比如超时、异常等,Hystris 能够保证在一个服务出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
Hystrix工作流程:
Hystrix整个工作流如下:
-
构造一个
HystrixCommand或HystrixObservableCommand对象,用于封装请求,并在构造方法配置请求被执行需要的参数; -
执行命令,Hystrix提供了4种执行命令的方法,后面详述;
-
判断是否使用缓存响应请求,若启用了缓存,且缓存可用,直接使用缓存响 - 应请求。Hystrix支持请求缓存,但需要用户自定义启动;
-
判断熔断器是否打开,如果打开,跳到第8步;
-
判断线程池/队列/信号量是否已满,已满则跳到第8步;
-
执行HystrixObservableCommand.construct()或HystrixCommand.run(),如果执行失败或者超时,跳到第8步;否则,跳到第9步;
-统计熔断器监控指标; -
走Fallback备用逻辑
-
返回请求响应
从流程图上可知道,第5步线程池/队列/信号量已满时,还会执行第7步逻辑,更新熔断器统计信息,而第6步无论成功与否,都会更新熔断器统计信息。
服务降级配置
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
启动类
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker//添加@EnableCircuitBreaker 或者 @EnableHystrix注解(可以使用@SpringCloudApplication代替多个注解)
public class OpenFeginMain {
public static void main(String[] args) {
SpringApplication.run(OpenFeginMain.class,args);
}
}
- 单独回调方法的配置
@HystrixCommand(fallbackMethod = "fallbackFun",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
@GetMapping("/to")
public String to(){
try {
Thread.sleep(4000);
log.info("to......................");
} catch (Exception e) {
e.printStackTrace();
}
return "sleep 4s..........";
}
public String fallbackFun(){
return "fallbackFun...........";
}
- 全局默认回调的配置
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "defaultFallback")
public class OpenFeignController {
@HystrixCommand
@GetMapping("/test")
public String test(){
log.info("test......................");
int i=0;
return "test................";
}
public String fallbackFun(){
return "fallbackFun...........";
}
}
- openFeign搭配Hystrix
application.yml
feign:
hystrix:
enabled: true
FeignService接口
@FeignClient(value = "Payment-Service",fallback = PaymentFallbackService.class)
@Component
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
public CommonResult get(@PathVariable("id") Long id);
}
fallback类
@Component
public class PaymentFallbackService implements PaymentFeignService{
@Override
public CommonResult get(Long id) {
return new CommonResult(444,"fallback...........",80);
}
}
服务熔断配置
application或者注解方式配置
# 配置熔断策略:
hystrix:
command:
default:
circuitBreaker:
# 原理分析中解释配置含义
# 强制打开熔断器 默认false关闭的。测试配置是否生效
# forceOpen: false
# 触发熔断错误比例阈值,默认值50%
errorThresholdPercentage: 50
# 熔断后休眠时长,默认值5秒
sleepWindowInMilliseconds: 10000
# 熔断触发最小请求次数,默认值是20
requestVolumeThreshold: 10
execution:
timeout:
enabled: true
isolation:
thread:
# 熔断超时设置,默认为1秒
timeoutInMilliseconds: 6000
// @HystrixCommand(fallbackMethod = "fallbackFun",commandProperties = {
// @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
// @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
// @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期
// @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),// 失败率达到多少后跳闸
// })
@HystrixCommand
@GetMapping("/test/{id}")
public String test(@PathVariable("id")Integer id){
log.info("test......................");
if(id<0){
throw new RuntimeException("e..........");
}
return "test.............";
}
测试:
- 正常情况下访问test/1返回fallback,test/1可以正常返回
- 当访问test/-1达到阈值后,熔断器开启。
- 此时访问test/1也访问到fallback。
- 继续访问几次test/1后恢复正常。
- 说明熔断器的状态由开启到半开启到关闭。