一、Hystrix的基础概述
1、分布式系统面临的问题?
复杂分布式体系结构中的应用程序有数十个依赖关系,某个依赖关系在某个时候将不可避免的失败。
以上图为例:
UserRequest请求需要调用A/P/H/I四个服务,如果一切顺利则没有什么问题,关键是如果I服务超时,会出现什么情况呢?
——服务雪崩
2、什么是服务雪崩?
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C有吊用其他的服务,这就是所谓的“扇出”——像扇子一样打开;
如果“扇出”的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而导致系统崩溃,所谓的“雪崩效应”;
对于高流量的应用来说,单一的后端依赖可能会导致服务器上的资源在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延时增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。
这些都表示需要对故障和延时进行隔离和管理,以便单个依赖关系的失败,不能拖垮整个应用程序或系统;
所以,通常当发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就发生了级联故障,即为服务雪崩!
3、什么是Hystrix?
Hystrix是一个用于处理分布式系统中的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的在某个时刻会出现调用失败的情况,比如超时、异常等;
Hystrix能够保证在一个依赖关系出现问题的情况下,不会导致整体服务失败、避免级联故障,以提高分布式系统的弹性!
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩!
4、Hystrix能干嘛?
-
服务降级:FallBack——服务器忙,请稍后再试,不让客户端等待并立即返回一个符合预期的、可处理的友好提示fallback——可能还可用
-
程序运行异常、超时、服务熔断触发服务降级、线程池/信号量打满也会导致服务降级
-
服务熔断:Break——保险丝,达到服务最大访问量后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示——比服务降级严重,直接不可用了
-
服务限流:FlowLimit——秒杀高并发等操作,严禁一窝蜂地过来拥挤,大家排队,一秒钟N个,有序进行
-
接近实时的监控:Hystrix Dashboard
二、Hystrix支付微服务构建 cloud-provider-hystrix-payment8001
1、pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud2020</artifactId> <groupId>com.jiguiquan.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-provider-hystrix-payment8001</artifactId> <dependencies> <!--hystrix--> <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>com.jiguiquan.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!--springboot项目web和actuator最好一起走--> <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> <!--热部署devtools--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> </project>
2、配置文件application.yml:
server: port: 8001 spring: application: name: cloud-provider-hystrix-payment eureka: client: register-with-eureka: true #表示向注册中心注册自己 默认为true fetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: defaultZone: http://localhost:7001/eureka # 单机版 入驻的EurekaServer地址
3、主启动类PaymentHystrixMain8001.java
@SpringBootApplication @EnableEurekaClient public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class, args); } }
4、业务处理PaymentService:
@Service public class PaymentService { //正常访问用的方法 public String paymentInfo_OK(Integer id){ return "线程池:" + Thread.currentThread().getName() + " PaymentInfo_OK, id:" + id; } //处理需要3秒钟,易造成超时的方法 public String paymentInfo_TimeOut(Integer id){ try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池:" + Thread.currentThread().getName() + " PaymentInfo_TimeOut, id:" + id + "耗时3秒钟"; } }
5、编写PaymentController
@RestController @Slf4j public class PaymentController { @Autowired private PaymentService paymentService; @GetMapping(value = "/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(value = "/payment/hystrix/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id){ String result = paymentService.paymentInfo_TimeOut(id); log.info("****result: " + result); return result; } }
6、启动确保此8001服务可用!
7、使用JMeter进行压测时,当20000个请求同时进来时,连"payment/hystrix/ok/{id}"接口也很卡顿,显然受到了timeout方法的影响;
三、创建Hystrix消费者服务 cloud-consumer-feign-hystrix-order80
1、pom.xml: 就比上面的提供者服务多一个feign的依赖:
<!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2、配置文件application.yml:
server: port: 80 eureka: client: register-with-eureka: false #表示向注册中心注册自己 默认为true service-url: defaultZone: http://localhost:7001/eureka # 单机版 入驻的EurekaServer地址
3、主启动类:
@SpringBootApplication @EnableFeignClients public class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class, args); } }
4、Feign注解接口:
@Service @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") public interface PaymentHystrixService { @GetMapping(value = "/payment/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id); @GetMapping(value = "/payment/hystrix/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id); }
5、业务OrderHystrixController
@RestController @Slf4j public class OrderHystrixController { @Autowired private PaymentHystrixService paymentHystrixService; @GetMapping(value = "/consumer/payment/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id){ return paymentHystrixService.paymentInfo_OK(id); } @GetMapping(value = "/consumer/payment/hystrix/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id){ return paymentHystrixService.paymentInfo_TimeOut(id); } }
6、启动OrderHystrix80的服务,进行测试:
正常情况下毫无问题:
但是当压力很大的时候,比如20000个请求的时候,连最简单的ok接口都有事会报超时的错误(即访问时间超过了Ribbon默认的1秒钟);
四、降级容错解决的维度要求
1、导致的原因:
8001统一层次的其他接口服务被困死,因为Tomcat线程池里面的工作线程已满
Order80此时调用8001中的接口,访问缓慢,运气不好就超时了;
2、解决的要求:
-
超时导致服务器变慢(转圈)——超时不再等待
-
出错(宕机或程序运行错误)——出错要有兜底
解决:
-
对方服务8001超时时,调用者80不能一直卡死等待,必须有服务降级;
-
对方服务8001宕机时,调用者80不能一直卡死等待,必须有服务降级;
-
对方服务8001OK时,调用者80自己出故障或有自我要求(自己能容忍的等待时间小于服务提供者80的响应时间,超时)
五、服务降级之服务提供者Payment侧Fallback
8001先从自身找原因,调节自己的超时时间峰值为3秒,超过了就需要Fallback,我们将8001的timeout方法时间设置为5秒;
1、业务类PaymentService 使用 @HystrixCommand 注解指定服务降级时的兜底方法:
@Service public class PaymentService { //正常访问用的方法 public String paymentInfo_OK(Integer id){ return "线程池:" + Thread.currentThread().getName() + " PaymentInfo_OK, id:" + id; } //指定服务降级兜底方法,并传参自身调用超时时间峰值为3秒 @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) public String paymentInfo_TimeOut(Integer id){ try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池:" + Thread.currentThread().getName() + " PaymentInfo_TimeOut, id:" + id + " 耗时5秒钟"; } public String paymentInfo_TimeOutHandler(Integer id){ return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOutHandler, id:" + id + " o(╥﹏╥)o"; } }
2、在主启动类上添加 @EnableCircuitBreaker
3、再次启动Payment8001服务,直接访问timeout接口:http://localhost:8001/payment/hystrix/timeout/2
很明显,Payment8001自身做了加强,实现了服务降级,而且服务降级使用的线程池是HystrixTimer提供的;
——这是服务提供者实现服务降级
六、服务降级之服务消费者Order侧Fallback
1、在配置文件中开始 feign_hystrix 的配置生效
feign: hystrix: enabled: true
2、主启动类增加注解:@EnableHystrix 开启功能
3、在 OrderHystrixController 中同样要增加一个兜底处理方法:
@RestController @Slf4j public class OrderHystrixController { @Autowired private PaymentHystrixService paymentHystrixService; @GetMapping(value = "/consumer/payment/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id){ return paymentHystrixService.paymentInfo_OK(id); } @GetMapping(value = "/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 "我是消费者Order80,对方Payment8001系统繁忙,请10秒钟后再试 o(╥﹏╥)o"; } }
4、重启Order80服务,访问: http://localhost/consumer/payment/hystrix/timeout/2
由此可见,服务消费者Order80侧的Fallback也可以正常工作!
服务提供者和服务消费者侧我们都做了服务降级Fallback处理,使得我们的系统更加的健壮!
个人此项目代码地址(持续更新):