Hystrix断路器
一、概述
分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候不可避免地失败。
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的 “扇出效应” 如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓“雪崩效应”
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的,可处理的备选响应(Fallback).而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用发的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
1. 作用
服务降级
服务熔断
接近实时的监控
2. 现状:停更进维
官网:https://github.com/Netflix/Hystrix/wiki/How-To-Use
Hystrix官宣,停更进维
Hystrix is no longer in active development, and is currently in maintenance mode.
二、Hystrix重要概念
1. 服务降级 FallBack
向调用方返回一个复合预期的可处理的备选响应,如 “服务器忙,请稍后再试!”不让客户等待并立刻返回一个友好提示。FallBack
哪些情况会触发降级
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
2. 服务熔断 Break
类似保险丝达到最大服务访问后,直接拒绝访问,然后用服务降级的方法返回友好提示。
就是保险丝->服务降级-进而熔断-恢复调用链路
3. 服务限流 Flowlimit
秒杀高并发等操作,严禁拥挤,排队1秒N个,有序进行
三、Hystrix 案例
1.构建
1.1 新建module
cloud-provider-hystrix-payment8001
1.2 pom
需引入 spring-cloud-starter-netflix-hystrix 和 Eureka
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1.3 yml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
1.4 main
@SpringBootApplication
@EnableEurekaClient
public class HystrixPaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(HystrixPaymentMain8001.class,args);
}
}
1.5 service
@Service
public class PaymentService {
/*正常访问OK的*/
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,ID: "+id;
}
public String paymentInfo_Timeout(Integer id) {
try{
TimeUnit.SECONDS.sleep(3);}
catch ( InterruptedException e){
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,ID:"+id;
}
}
1.6 controller
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id")Integer id){
String result=paymentService.paymentInfo_OK(id);
log.info("****result:"+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
String result=paymentService.paymentInfo_Timeout(id);
log.info("****result:"+result);
return result;
}
}
1.7 启动本服务 和 Eureka服务
1.8 查看正常访问耗时
3000ms
不到10ms
2.Jmeter高并发测试8001
2.1 新建线程组
2.2 开启压测后 两个接口耗时明显增多
开启压测后查看耗时
ok:1700ms
timeout: 4200ms
tomcat的默认工作线程数被打满了,没有多余的线程来分解压力和处理。
2.3 结论
上面是服务提供者自测,如果消费者访问,只能干等,最终导致消费端80不满意,服务端8001直接被拖死。
3.新建消费者80 使用消费者测试接口
3.1. 新建module cloud-consumer-feign-hystrix-order80
3.2 pom
<dependencies>
<!--open feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.zhl.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.3. yml
server:
port: 80
spring:
application:
name: cloud-consumer-feign-hystrix-order80
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
3.4 main
@SpringBootApplication
@EnableFeignClients
public class FeignHystixOrderMainF80 {
public static void main(String[] args) {
SpringApplication.run(FeignHystixOrderMainF80.class,args);
}
}
3.5 Service
com.zhl.springcloud.service.PaymentHystrixService
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id")Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id")Integer id);
}
3.6 Controller
com.zhl.springcloud.controller.OrderHystrixController
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id")Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
}
3.7 调用测试
测试OK接口 耗时8ms
开启JMeter进行压测后,再访问
4.故障现象和导致原因
8001 访问繁忙,客户端相应缓慢。
5.结论
正因为有上述故障或不佳表现,才有 降级、容错、限流技术诞生。
6.如何解决?解决的需求
- 服务提供者超时、服务器变慢:消费者不能一直等待,必须有服务降级
- 服务提供者出错(宕机或程序运行出错):消费者不能一直等待,必须有服务降级
- 消费者等待时间小于服务提供者运行时间,消费者自己处理降级。
7. 服务降级
7.1.降级配置
@HystrixCommand
7.2 服务提供者分析
设置自身调用超时时间峰值,峰值内可以正常运行超过了需要有兜底的方法处理,作服务降级fallback
7.3 服务提供者8001服务降级代码优化
一旦调用服务方法失败并抛出了错误信息后会自动调用@HystrixCommand标注好的fallbackMethod调用类中指定的方法
超时 或 降级 都由fallbackMethod处理
使用@HystrixCommand 注解 标注超时处理方法,
7.4 服务提供者8001服务降级优化
7.4.1 Service:
com.zhl.springcloud.service.PaymentService
@Service
public class PaymentService {
/*正常访问OK的*/
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,ID: "+id;
}
/*超时 或 降级 都由fallbackMethod处理*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
//正常超时时间3秒,超时执行fallbackMethod
@HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String paymentInfo_Timeout(Integer id) {
int timenumner=5;
try{
TimeUnit.SECONDS.sleep(timenumner);}
catch ( InterruptedException e){
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,ID:"+id+"\t"+"耗时:"+timenumner;
}
/*降级处理*/
public String paymentInfo_TimeoutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,ID:"+id+"\t"+"降级处理";
}
}
7.4.2 主启动类激活 添加新注解 @EnableCircuitBreaker
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class HystrixPaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(HystrixPaymentMain8001.class,args);
}
}
7.4.3 超时降级测试
服务需要5秒,3秒返回降级信息。
7.4.4 异常测试
修改业务类
/*超时 或 降级 都由fallbackMethod处理*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
//正常超时时间3秒,超时执行fallbackMethod
@HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String paymentInfo_Timeout(Integer id) {
int timenumner=5;
/*测试报错*/
int age=10/0;
try{
TimeUnit.SECONDS.sleep(timenumner);}
catch ( InterruptedException e){
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,ID:"+id+"\t"+"耗时:"+timenumner;
}
/*降级处理*/
public String paymentInfo_TimeoutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+" 系统繁忙或运行保存,请稍后再试,ID:"+id+"\t"+"降级处理";
}
访问测试:
7.5 消费者服务80 服务降级
7.5.1 YML 配置OpenFeign 支持 Hystrix
server:
port: 80
spring:
application:
name: cloud-consumer-feign-hystrix-order80
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
#OpenFeign开启Hystrix
feign:
hystrix:
enabled: true
7.5.2 Main 开启熔断器@EnableCircuitBreaker
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
public class FeignHystixOrderMainF80 {
public static void main(String[] args) {
SpringApplication.run(FeignHystixOrderMainF80.class,args);
}
}
7.5.3 Controller
com.zhl.springcloud.controller.OrderHystrixController
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id")Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
/*1.5秒超时*/
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
@HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
/*降级处理*/
public String paymentInfo_TimeoutHandler(Integer id){
return "我是消费者80,对方支付系统繁忙,请10秒钟后再试或者自己运行出错请检查自己 (╥╯^╰╥)";
}
}
7.5.4 服务提供者业务代码 5秒超时,3秒业务处理时间
@Service
public class PaymentService {
/*正常访问OK的*/
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,ID: "+id;
}
/*超时 或 降级 都由fallbackMethod处理*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
//正常超时时间3秒,超时执行fallbackMethod
@HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "5000")
})
public String paymentInfo_Timeout(Integer id) {
int timenumner=3;
/*测试报错*/
try{
TimeUnit.SECONDS.sleep(timenumner);}
catch ( InterruptedException e){
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,ID:"+id+"\t"+"耗时:"+timenumner;
}
/*降级处理*/
public String paymentInfo_TimeoutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+" 系统繁忙或运行保存,请稍后再试,ID:"+id+"\t"+"降级处理";
}
}
7.5.5 超时测试效果 提供者3秒 消费者1.5秒
7.5.6 消费者自己出错测试
int age=10/0 测试出错
/*1.5秒超时*/
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
@HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
int age=10/0;
return paymentHystrixService.paymentInfo_TimeOut(id);
}
/*降级处理*/
public String paymentInfo_TimeoutHandler(Integer id){
return "我是消费者80,对方支付系统繁忙,请10秒钟后再试或者自己运行出错请检查自己, (╥╯^╰╥)";
}
7.6 服务降级全局配置
目前每个业务对应一个兜底的方法,代码膨胀,需要统一和自定义分开。
7.6.1 Controller级别配置
在Controller加@DefaultPropertie注解,指定控制器内全局处理方法 .
com.zhl.springcloud.controller.OrderHystrixController
在接口上只引用@HystrixCommand ,不用指定fallbackMethod.
定义一个Controller全局的处理方法,这里为ControllerGlobalFallBack
注意:
接口级别的Fallback Method需要参数与返回类型与接口一致。
类级别的 FallbackMethod 这里为ControllerGlobalFallBack 不能有参数,否则报错 fallback method wasn't found:
@RestController
@Slf4j
/*Controller级别默认处理方法*/
@DefaultProperties(defaultFallback = "ControllerGlobalFallBack")
public class OrderHystrixController {
@Resource
PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id")Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
/*1.5秒超时*/
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
/*自定义指定处理方法*/
/*@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
@HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})*/
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
//int age=10/0;
return paymentHystrixService.paymentInfo_TimeOut(id);
}
/*降级处理*/
public String paymentInfo_TimeoutHandler(Integer id){
return "我是消费者80,对方支付系统繁忙,请10秒钟后再试或者自己运行出错请检查自己, (╥╯^╰╥)";
}
/*Controller全局FallBack处理*/
public String ControllerGlobalFallBack(){
return "ControllerGlobalFallBack";
}
}
7.6.2 服务调用接口统一配置
新建一个类 实现FeignClient所在接口
@Component
public class HystrixPaymentFallBackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "---HystrixPaymentServiceImpl_paymentInfo_OK fallback ";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "----HystrixPaymentServiceImpl_paymentInfo_TimeOut fallback";
}
}
YML开启Feign
#OpenFeign开启Hystrix
feign:
hystrix:
enabled: true
controller 中取消 @HystrixCommand 的注解
@RestController
@Slf4j
/*Controller级别默认处理方法*/
//@DefaultProperties(defaultFallback = "ControllerGlobalFallBack")
public class OrderHystrixController {
@Resource
PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id")Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
/*1.5秒超时*/
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
/*自定义指定处理方法*/
/*@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
@HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})*/
/* @HystrixCommand*/
public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
//int age=10/0;
return paymentHystrixService.paymentInfo_TimeOut(id);
}
/*降级处理*/
public String paymentInfo_TimeoutHandler(Integer id){
return "我是消费者80,对方支付系统繁忙,请10秒钟后再试或者自己运行出错请检查自己, (╥╯^╰╥)";
}
/*Controller全局FallBack处理*/
public String ControllerGlobalFallBack(){
return "ControllerGlobalFallBack";
}
}
访问timeout接口测试