SpringCloud10-Hystrix熔断器

    科技2022-08-28  114

    SpringCloud10-Hystrix熔断器

    SpringCloud系列文档地址: SpringCloud 进阶之路

    1、概述 1.1 分布式系统面临的问题 复杂分布式体系结构中的应用程序, 有数10个依赖关系,每个依赖关系在某些时候将不可避免地失败;

    1.2 服务雪崩 多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统奔溃,这就是所谓的“雪崩效应”。

    对于高流量的应用来说,单一的后端依赖可能会导致所有的服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

    所以,通常当你发现一个模块下的某个实例失败后,这个时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

    1.3 Hystrix是什么 Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等;Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

    “断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),**向调用方返回一个符合预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,**这样就保证了服务调用方的线程不会被长时间,不必要地占用,从而避免了故障在分布式系统钟的蔓延,乃至雪崩。

    1.4 Hystrix能做什么

    服务降级 服务熔断 接近实时的监控

    github的地址: https://github.com/Netflix/hystrix/wiki

    1.5 Hystrix官宣,停更进维 https://github.com/Netflix/hystrix:查看

    Hystrix is no longer in active development, and is currently in maintenance mode(Hystrix不再处于积极开发中,目前处于维护模式)

    停更进维 就代表:被动修复bugs,不再接受合并请求,不再发布新版本

    2、Hystrix重要概念 参考博客地址:https://www.cnblogs.com/huangjuncong/p/9026949.html

    2.1 服务降级 Fallback相当于是降级操作。对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候,可以使用fallback方法返回的值。fallback方法的返回值一般是设置的默认值或者来自缓存,告知后面的请求服务不可用了,不要再来了。

    哪些情况会发生服务降级:

    程序运行异常 超时 服务熔断触发服务降级 线程池/信号量也会导致服务降级 2.2 服务熔断 类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。服务的降级->进而熔断->恢复调用链路

    当Hystrix Command请求后端服务失败数量超过一定比例(默认50%),断路器会切换到开路状态(Open)。 这时所有请求会直接失败而不会发送到后端服务,断路器保持在开路状态一段时间后(默认5秒),自动切换到半开路状态(HALF-OPEN)。

    这时会判断下一次请求的返回情况, 如果请求成功,断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN), Hystrix的断路器就像我们家庭电路中的保险丝,一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力。

    2.3 服务限流 秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。

    3、Hystrix的案例 3.1 构建cloud-provider-hystrix-payment8001 3.1.1 建Module

    3.1.2 改pom

    <?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>springcloud2020</artifactId> <groupId>com.zdw.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.zdw.springcloud</groupId> <artifactId>cloud-api-common</artifactId> <version>1.0-SNAPSHOT</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> </project>

    3.1.3 写yml

    server: port: 8001 spring: application: name: cloud-provider-hystrix-payment eureka: client: register-with-eureka: true fetch-registry: true service-url: # 单击版 # defaultZone: http://localhost:7001/eureka # 集群版 defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

    3.1.4 主启动

    package com.zdw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class,args); } }

    3.1.5 业务代码 1、service代码

    package com.zdw.springcloud.service; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class PaymentService { /** * 正常访问 */ public String paymentInfo_OK(Integer id) { return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id:" + id + "\t" + "O(∩_∩)O哈哈~"; } /** * 超时访问 */ public String paymentInfo_TimeOut(Integer id) { ///int age = 10 / 0; int timeNumber = 5; try { // 暂停5秒钟 TimeUnit.SECONDS.sleep(timeNumber); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" + id + "\t" + "O(∩_∩)O哈哈~ 耗时(秒)" + timeNumber; } }

    2、controller代码

    package com.zdw.springcloud.controller; import com.zdw.springcloud.service.PaymentService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j public class PaymentHystrixController { @Autowired PaymentService paymentService; @Value("${server.port}") private String servicePort; /** * 正常访问 */ @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; } }

    3.1.6 测试 3.1.6.2 正常测试 启动Eureka7001和Eureka7002,然后启动cloud-provider-hystrix-payment8001,

    访问:http://localhost:8001/payment/hystrix/ok/1 应该瞬间就返回结果:线程池:http-nio-8001-exec-1 paymentInfo_OK,id:1 O(∩_∩)O哈哈~

    访问:http://localhost:8001/payment/hystrix/timeout/1 应该是浏览器会转圈,5秒之后会返回结果:线程池:http-nio-8001-exec-2 paymentInfo_TimeOut,id:1 O(∩_∩)O哈哈~ 耗时(秒)5

    可以看到,两个请求处理的线程都是不同的,Hystrix是使用的Tomcat的线程。

    3.1.6.3 高并发测试 Jmeter压力测试

    Jmeter的使用和配置,具体可以参照我的这篇博客:https://blog.csdn.net/zengdongwen/article/details/94392892

    本次的配置如下:

    压力测试开始;

    然后我们在浏览器访问:http://localhost:8001/payment/hystrix/ok/1 ,这个本来是秒回的,现在也开始转圈圈,需要等待一会才能返回结果。

    这是因为我们通过Jemter,大量的请求:http://localhost:8001/payment/hystrix/timeout/1,导致,Tomcat的线程池都会去处理这些请求,资源不够,需要等待。tomcat的默认工作线程数被打满了,没有多余的线程来分解压力和处理。

    压力测试结论

    上面还只是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死。

    3.2 构建cloud-consumer-feign-hystrix-order80 3.2.1 建Module

    3.2.2 改pom

    <?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>springcloud2020</artifactId> <groupId>com.zdw.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-consumer-feign-hystrix-order80</artifactId> <dependencies> <!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!--openfeign--> <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.zdw.springcloud</groupId> <artifactId>cloud-api-common</artifactId> <version>1.0-SNAPSHOT</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> </project>

    3.2.3 写yml

    server: port: 80 spring: application: name: cloud-consumer-feign-hystrix-order eureka: client: register-with-eureka: true fetch-registry: true service-url: # 单击版 # defaultZone: http://localhost:7001/eureka # 集群版 defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

    3.2.4 主启动

    package com.zdw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableEurekaClient @EnableFeignClients public class OrderFeignHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderFeignHystrixMain80.class,args); } }

    3.2.5 业务代码 1、Feign接口

    package com.zdw.springcloud.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Component @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") public interface PaymentHystrixService { /** * 正常访问 */ @GetMapping("/payment/hystrix/ok/{id}") String paymentInfo_OK(@PathVariable("id") Integer id); /** * 超时访问 */ @GetMapping("/payment/hystrix/timeout/{id}") String paymentInfo_TimeOut(@PathVariable("id") Integer id); }

    2、controller代码

    package com.zdw.springcloud.controller; import com.zdw.springcloud.service.PaymentHystrixService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j public class OrderHystrixController { @Autowired PaymentHystrixService paymentHystrixService; /** * 正常访问 * http://localhost/consumer/payment/hystrix/ok/1 */ @GetMapping("/consumer/payment/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id) { return paymentHystrixService.paymentInfo_OK(id); } /** * 超时访问 * http://localhost/consumer/payment/hystrix/timeout/1 */ @GetMapping("/consumer/payment/hystrix/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { //int age = 10/0; return paymentHystrixService.paymentInfo_TimeOut(id); } }

    3.2.6 测试 1、正常测试 浏览器访问:http://localhost/consumer/payment/hystrix/ok/1 应该是秒出结果的

    2、压力测试 继续开启上面的压力测试,20000个请求发送给:http://localhost:8001/payment/hystrix/timeout/1

    然后再次浏览器访问:http://localhost/consumer/payment/hystrix/ok/1

    有两种结果:

    1、浏览器等待一会之后,正常返回结果; 2、直接报500,因为我们集成了OpenFeign,客户端默认1秒之后就会超时,然后报错:

    3.3 故障分析 3.3.1 出现故障的原因 8001同一层次的其他接口被困死,因为tomcat线程池里面的工作线程已经被挤占完毕;80此时调用8001,客户端访问响应缓慢,转圈圈;如果实在太慢,因为OpenFeign会直接报500错误。

    正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生。

    3.3.2 解决问题的方向 问题:超时导致服务器变慢(转圈);解决方式:超时不再等待;

    问题:出错(宕机或程序运行出错);解决方式:出错要有兜底(备选方案);

    对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级; 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级; 对方服务(8001)ok,调用者(80)自己有故障或有自我要求(自己的等待时间小于服务提供者) 3.4 服务降级 降级配置的重要注解:@HystrixCommand

    3.4.1 8001的服务降级处理 8001先从自身找问题:设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,做服务降级fallback。

    1、业务类添加注解@HystrixCommand

    package com.zdw.springcloud.service; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class PaymentService { /** * 正常访问 */ public String paymentInfo_OK(Integer id) { return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id:" + id + "\t" + "O(∩_∩)O哈哈~"; } /** * 超时访问 * HystrixCommand:一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbckMethod调用类中的指定方法 * execution.isolation.thread.timeoutInMilliseconds:线程超时时间3秒钟 */ @HystrixCommand(fallbackMethod = "payment_TimeOutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) public String paymentInfo_TimeOut(Integer id) { //int age = 10 / 0; int timeNumber = 5; try { // 暂停5秒钟 TimeUnit.SECONDS.sleep(timeNumber); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" + id + "\t" + "O(∩_∩)O哈哈~ 耗时(秒)" + timeNumber; } /** * 兜底方案 */ public String payment_TimeOutHandler(Integer id) { return "线程池:" + Thread.currentThread().getName() + " 系统繁忙或运行错误,请稍后重试,id:" + id + "\t" + "o(╥﹏╥)o"; } }

    paymentInfo_TimeOut 方法上面使用注解:@HystrixCommand 指定了服务降级的配置,指定了兜底方法:payment_TimeOutHandler,配置了3秒之后,服务降级生效,便会执行兜底方法:payment_TimeOutHandler。不会让客户端去死等。

    一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbckMethod调用类中的指定方法。

    2、主启动类激活

    package com.zdw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class,args); } }

    @EnableCircuitBreaker 添加这个注解,表示开启服务降级

    注意:我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务

    3、测试 重新启动8001和80之后,浏览器访问:http://localhost:8001/payment/hystrix/timeout/1 这个会调用8001的的服务,它的逻辑是要处理5秒的,但是现在我们进行了服务降级之后,3秒之后就会返回:线程池:HystrixTimer-2 系统繁忙或运行错误,请稍后重试,id:1 o(╥﹏╥)o

    当我们放开注释:int age = 10 / 0; 然后再次访问 http://localhost:8001/payment/hystrix/timeout/1 会看到浏览器会瞬间返回:线程池:hystrix-PaymentService-7 系统繁忙或运行错误,请稍后重试,id:1 o(╥﹏╥)o ,说明超时和异常都会导致服务降级。

    3.4.2 order80的服务降级 Hystrix的服务降级可以放在服务端,也可以放在消费端,但是一般都是放在客户(消费)端来做服务降级,所以order80的服务降级更加需要掌握。 1、pom文件加入hystrix org.springframework.cloud spring-cloud-starter-netflix-hystrix

    这个坐标我们之前就已经添加过了

    2、改yml

    feign: hystrix: enabled: true

    开启hystrix,frign是顶格写的

    3、主启动激活

    package com.zdw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableEurekaClient @EnableFeignClients @EnableHystrix public class OrderFeignHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderFeignHystrixMain80.class,args); }

    }

    @EnableHystrix 激活Hystrix

    4、修改controller

    package com.zdw.springcloud.controller; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.zdw.springcloud.service.PaymentHystrixService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j public class OrderHystrixController { @Autowired PaymentHystrixService paymentHystrixService; /** * 正常访问 * http://localhost/consumer/payment/hystrix/ok/1 */ @GetMapping("/consumer/payment/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id) { return paymentHystrixService.paymentInfo_OK(id); } /** * 超时访问 * http://localhost/consumer/payment/hystrix/timeout/1 */ @GetMapping("/consumer/payment/hystrix/timeout/{id}") @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500") }) public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { //int age = 10/0; return paymentHystrixService.paymentInfo_TimeOut(id); } //兜底的方法 public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) { return "我是消费者80,对方支付系统繁忙!请10秒种后再试或者自己运行出错请检查自己,o(╥﹏╥)o"; } }

    给我们的OrderHystrixController的paymentInfo_TimeOut方法也用@HystrixCommand修饰,指定超时时间是500毫秒(本来Feign的默认超时时间是 1000 毫秒),兜底的方法是paymentTimeOutFallbackMethod。

    5、测试 重新启动order80,然后浏览器器访问:http://localhost/consumer/payment/hystrix/timeout/1 可以看到一会之后,浏览器直接返回:我是消费者80,对方支付系统繁忙!请10秒种后再试或者自己运行出错请检查自己,o(╥﹏╥)o ,说明服务降级生效了。

    如果我们放开注释的: int age = 10/0; ,等待重启之后,访问浏览器,会看到立马就返回:我是消费者80,对方支付系统繁忙!请10秒种后再试或者自己运行出错请检查自己,o(╥﹏╥)o,所以客户端的服务降级和8001的服务降级是一样的,超时和异常都会导致服务降级。

    3.4.3 指定默认的全局兜底方法 上面的服务降级,我们是针对每个方法都要去指定兜底的fallbackMethod,这样比较繁琐。因此可以指定一个默认的兜底方法,当没有指定具体的兜底方法时,则会去找这个全局的兜底方法。

    修改OrderHystrixController:

    package com.zdw.springcloud.controller; import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.zdw.springcloud.service.PaymentHystrixService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j @DefaultProperties(defaultFallback = "globalFallbackMethod") public class OrderHystrixController { @Autowired PaymentHystrixService paymentHystrixService; /** * 正常访问 * http://localhost/consumer/payment/hystrix/ok/1 */ @GetMapping("/consumer/payment/hystrix/ok/{id}") @HystrixCommand public String paymentInfo_OK(@PathVariable("id") Integer id) { int i = 1/0; return paymentHystrixService.paymentInfo_OK(id); } /** * 超时访问 * http://localhost/consumer/payment/hystrix/timeout/1 */ @GetMapping("/consumer/payment/hystrix/timeout/{id}") /*@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2500") })*/ @HystrixCommand public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { //int age = 10/0; return paymentHystrixService.paymentInfo_TimeOut(id); } //兜底的方法,该方法的参数个数和类型等一定要和主方法的一样的 public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) { return "我是消费者80,对方支付系统繁忙!请10秒种后再试或者自己运行出错请检查自己,o(╥﹏╥)o"; } //默认的全局兜底的方法,该方法不能有任何参数 public String globalFallbackMethod() { return "Global异常处理信息,请稍后重试.o(╥﹏╥)o"; } }

    修改点:

    1 paymentInfo_OK方法上面添加了注解:@HystrixCommand,也会进行服务降级,里面特意写了一句出异常的: int i = 1/0; 2 paymentInfo_TimeOut方法上面只添加了注解:@HystrixCommand,所以最终也会去找全局的兜底方法 3 globalFallbackMethod新增了一个全局兜底方法,(注意该方法不能有任何参数,否则报错) 4 类上新增了注解:@DefaultProperties(defaultFallback = “globalFallbackMethod”) 指定了默认的全局兜底的方法(试过把defaultFallback 指定为另一个类的方法,但是会报错,找不到那个方法,这个需要研究) 重新启动order80,测试:

    浏览器访问:http://localhost/consumer/payment/hystrix/ok/1 和 http://localhost/consumer/payment/hystrix/timeout/1 都可以看到浏览器返回:Global异常处理信息,请稍后重试.o(╥﹏╥)o

    3.4.4 在客户端对服务端的方法进行服务降级 上面我们处理服务降级的方法都是定义在OrderHystrixController种,跟我们的业务处理方法耦合在了一起,最好的方式应该是服务降级的方法单独定义,不要和我们的业务方法混合在一起。并且我们上面在order80里面的服务降级是客户端的处理,处理的是客户端;

    这个时候我们就要把眼光放到业务接口:PaymentHystrixService,这个接口里面定义所有我们的服务调用的方法,所以针对它做统一的服务降级处理。因为注解:@FeignClient 里面也有一个属性:fallback,可以指定我们的服务降级的处理类。并且这个服务降级是对服务端的方法的服务降级的兜底处理。

    1、新增一个PaymentHystrixService的实现类

    package com.zdw.springcloud.service; import org.springframework.stereotype.Component; @Component public class PaymentHystrixServiceFallback implements PaymentHystrixService { @Override public String paymentInfo_OK(Integer id) { return "我是paymentInfo_OK的兜底方法,服务降级的时候由我来处理..."; } @Override public String paymentInfo_TimeOut(Integer id) { return "我是paymentInfo_TimeOut的兜底方法,服务降级的时候由我来处理..."; } }

    @Component 一定要加这个,要交给IOC容器管理

    2、PaymentHystrixService添加fallback

    @FeignClient(value = “CLOUD-PROVIDER-HYSTRIX-PAYMENT”,fallback = PaymentHystrixServiceFallback.class)

    3、修改OrderHystrixController OrderHystrixController 方法的@HystrixCommand注解要去掉,不然的话还是会由全局兜底方法去执行服务降级。

    然后还要注释掉:paymentInfo_OK方法的 int i = 1/0;,不然的话我们已经没有对他加注解@HystrixCommand,那么就会直接报错了。

    4、修改8001的PaymentService的paymentInfo_OK方法 在paymentInfo_OK种添加一个出异常的语句:int i= 1/0;

    5、测试 浏览器访问:http://localhost/consumer/payment/hystrix/timeout/1 返回:我是paymentInfo_TimeOut的兜底方法,服务降级的时候由我来处理…

    浏览器访问:http://localhost/consumer/payment/hystrix/ok/1 返回:我是paymentInfo_OK的兜底方法,服务降级的时候由我来处理…

    宕机测试:

    我们关闭8001,然后浏览器再次访问上面的地址:也能正常的返回兜底方法的信息。

    此时服务端provider已经down,但是我们做了服务降级处理, 让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。

    3.5 服务熔断 3.5.1 概述 熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。

    当检测到该节点微服务调用响应正常后,恢复调用链路。

    在Spring Cloud框架种,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用达到一定的阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是:@HystrixCommand。

    马丁弗勒的关于熔断的论文:https://martinfowler.com/bliki/CircuitBreaker.html

    3.5.2 在8001演示服务熔断 在微服务:cloud-provider-hystrix-payment8001 进行服务熔断的操作

    1、修改PaymentService 添加如下关于熔断的代码:

    //-----------------------------下面的代码是关于服务熔断的----------------------- /** * 在10秒窗口期中10次请求有6次是请求失败的,断路器将起作用 */ @HystrixCommand( fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),// 时间窗口期/时间范文 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")// 失败率达到多少后跳闸 } ) public String paymentCircuitBreaker(@PathVariable("id") Integer id) { if (id < 0) { throw new RuntimeException("*****id不能是负数"); } String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber; } public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) { return "id 不能负数,请稍后重试,o(╥﹏╥)o id:" + id; }

    @HystrixCommand的配置都在HystrixCommandProperties

    2、修改PaymentHystrixController 在 PaymentHystrixController 种添加熔断的方法

    /** * 服务熔断 * http://localhost:8001/payment/circuit/32 */ @GetMapping("/payment/circuit/{id}") public String paymentCircuitBreaker(@PathVariable("id") Integer id) { String result = paymentService.paymentCircuitBreaker(id); log.info("***result:" + result); return result; }

    3、测试 启动8001之后:

    浏览器访问:http://localhost:8001/payment/circuit/1 返回:hystrix-PaymentService-1 调用成功,流水号:9bcc88da3e534bca9d87a2c3e1189ace

    浏览器访问:http://localhost:8001/payment/circuit/-1 返回:id 不能负数,请稍后重试,o(╥﹏╥)o id:-1

    熔断测试:

    我们不停的访问:http://localhost:8001/payment/circuit/-1 大概10秒之后,再访问正常返回的:http://localhost:8001/payment/circuit/1 会发现也是返回的:id 不能负数,请稍后重试,o(╥﹏╥)o id:-1,只有多访问几次之后,才会慢慢的返回正常的:hystrix-PaymentService-10 调用成功,流水号:f24e78dd2cd04a0ea384af8e0493eadd

    3.5.3 熔断器的三个状态 在马丁弗勒的论文里面,有一个这样的图:

    Open:请求不再调用当前服务,内部设置一般为MTTR(平均故障处理时间),当打开时长达到所设时钟;则进入半熔断状态。 Half Open:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断。 Closed:熔断关闭后不会对服务进行熔断。

    电路断开和闭合的精确方式如下:

    1 假设通过电路的卷满足某个阈值(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())。。。

    2 并且假设错误百分比超过阈值错误百分比(HystrixCommandProperties.circuitrebreakererrorthresholdpertage())。。。

    3 然后断路器从闭合过渡到断开。

    4 当断路器断开时,它会使针对该断路器的所有请求短路。

    5 经过一段时间(HystrixCommandProperties.circuitbreakersleepwinindowmillishes())后,下一个请求被允许通过(这是半开状态)。如果请求失败,断路器将在睡眠窗口期间返回打开状态。如果请求成功,断路器将转换为闭合,逻辑输入1。再次接管

    3.5.4 熔断器的工作流程 **流程图:**下图显示了使用Hystrix向服务依赖项发出请求时发生的情况:

    流程图详解:

    Construct a HystrixCommand or HystrixObservableCommand Object :构造HystrixCommand或hystrixobservatablecommand对象 Execute the Command :执行命令 Is the Response Cached? :是否缓存响应? Is the Circuit Open? :是否是开路状态? Is the Thread Pool/Queue/Semaphore Full? :线程池/队列/信号量是否已满? HystrixObservableCommand.construct() or HystrixCommand.run() Calculate Circuit Health :计算电路运行状况 Get the Fallback :得到兜底方法 Return the Successful Response :返回成功的响应

    参考网址: https://blog.csdn.net/zengdongwen/article/details/105790273

    本次课程的讲解是尚硅谷的周阳老师,如有需要请购买正版书籍或网上到尚硅谷官网进行学习。

    Processed: 0.018, SQL: 9