场景
SpringCloud中集成Eureka实现服务注册(单机Eureka构建):
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124688609
SpringCloud中集成Eureka实现集群部署服务注册与服务提供者:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124710576
SpringCloud中集成OpenFeign实现服务调用:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124822349
在上面实现服务提供与服务消费的基础上,下面学习Hystrix的使用。
分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,B和C又调用其他的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒内饱和。比失败更槽糕的是这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以当发现一个模块下的某个实例失败后,这时候整个模块仍然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
Hystrix
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等。Hystrix能够保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以调高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可被处理的备选响应,而不是长时间的等待或者抛出调用方法无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
官网使用教程
https://github.com/Netflix/Hystrix/wiki/How-To-Use
服务降级
服务器繁忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
哪些情况会触发降级?
1、程序运行异常
2、超时
3、服务熔断触发服务降级
4、线程池/信号量打满也会导致服务降级
服务熔断
类比保险丝达到最大服务访问之后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
服务的降级-进而熔断-恢复调用链路。
服务限流
秒杀等高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒中N个,有序进行。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。
实现
1、模拟高并发测试
为了构造出高并发的场景,按照上面新建8001服务提供者的流程,新建子模块cloud-provide-hystrix-payment8001
修改pom文件添加hystrix的依赖
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
这里继续使用Eureka作为服务注册中心,所以还需要引入其他依赖
<?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>SpringCloudDemo</artifactId> <groupId>com.badao</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-provider-hystrix-payment8001</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <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><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --> <groupId>com.badao</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> </dependencies> </project>
2、使用Eureka单机部署进行注册,所以新建并修改application.yml
server: port: 8001 spring: application: name: cloud-provider-hystrix-payment eureka: client: register-with-eureka: true fetch-registry: true service-url: #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka defaultZone: http://eureka7001.com:7001/eureka
3、新建启动类并添加@EnableEurekaClient注解
package com.badao.springclouddemo; 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); } }
4、新建service并新增两个方法,一个正常访问返回,一个模拟延迟3秒返回的延迟效果
package com.badao.springclouddemo.service; import cn.hutool.core.util.IdUtil; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.PathVariable; import java.util.concurrent.TimeUnit; @Service public class PaymentService { /** * 正常访问,肯定OK * @param id * @return */ public String paymentInfo_OK(Integer id) { return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~"; } public String paymentInfo_TimeOut(Integer id) { try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): "; } }
5、新建Controller,实现对service的调用
package com.badao.springclouddemo.controller; import com.badao.springclouddemo.service.PaymentService; import lombok.extern.slf4j.Slf4j; 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; import javax.annotation.Resource; @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; } }
6、启动Eureka Server7001,然后启动上面8001服务提供者。
此时访问这两个接口
此时再这种底并发的请求场景下,延迟的请求不会影响正常的请求效果,正常请求基本是秒反应。
7、模拟高并发对延迟请求接口进行20000的高并发测试
这里使用JMeter进行压力测试
Jmeter进行http接口压力测试:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124928498
参考上面的教程,此时对带延迟的接口进行高并发的压力测试,此时再同时调用正常的接口,响应时间变长,
接口也会受牵连。
因为Tomcat的默认工作线程数被打满了,没有多余的线程来分解压力和处理。
上面只是服务提供者8001自己测试,如果服务消费者此时也来请求,只能等待,最终导致消费者不满意,
提供者8001直接被拖死。
8、服务消费者调用测试
新建子模块cloud-consumer-feign-hystrix-order88
修改pom文件,添加hystrix的依赖
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
这里还需要使用openfeign的依赖,所以完整的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>SpringCloudDemo</artifactId> <groupId>com.badao</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-consumer-feign-hystrix-order88</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <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><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --> <groupId>com.badao</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> </dependencies> </project>
新建并修改application.yml
server: port: 88 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/ #设置feign客户端超时时间(OpenFeign默认支持ribbon) ribbon: #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 ReadTimeout: 3500 #指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 3500
配置eureka和feign的相关配置,上面配置的服务中有个延迟3秒的服务,这里设置feign客户端超时时间为3.5秒。
新建并修改启动类
package com.badao.springclouddemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients public class OrderHystrixMain88 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain88.class,args); } }
新建service
package com.badao.springclouddemo.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}") public String paymentInfo_OK(@PathVariable("id") Integer id); @GetMapping("/payment/hystrix/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id); }
新建controller
package com.badao.springclouddemo.controller; import com.badao.springclouddemo.service.PaymentHystrixService; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Slf4j public class OrderHystirxController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping("/consumer/payment/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_OK(id); return result; } @GetMapping("/consumer/payment/hystrix/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } }
这块的流程可以参考
SpringCloud中集成OpenFeign实现服务调用:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124822349
9、在前面使用JMeter以及服务提供者自测的基础上,再启动服务消费者88,此时调用带延迟返回的接口,当响应时间超过3.5秒时会出现超时错误
10、解决的要求
超时导致服务器变慢-超时不再等待
出错(宕机或者程序运行出错)-出错要有兜底,即最终要返回的结果
所以
当服务提供者8001超时了,服务消费者88不能一直等待,要有服务降级
当8001宕机,调用者也不能一直等待,必须有服务降级
当8001正常,88自己出故障或者自我要求的等待时间小于服务提供者的响应时间,88自己处理降级
11、服务提供者8001服务降级配置
服务提供者8001先从自身找问题:
设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作为服务降级的
fallback。
首先在业务类中启动@HystrixCommand注解,配置fallbackMethod属性,一旦调用服务方法失败并抛出了错误信息
之后,会自动调用指定方法。
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") }) public String paymentInfo_TimeOut(Integer id) { //int age = 10/0; try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): "; } public String paymentInfo_TimeOutHandler(Integer id) { return "线程池: "+Thread.currentThread().getName()+" 8001系统繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"o(╥﹏╥)o"; }
注意这里上面还添加了
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
这个意思是指3秒之内可以接收,3秒之后认定为超时异常。
fallbackMethod = "paymentInfo_TimeOutHandler",就是兜底的处理方法,当服务降级时就会调用下面声明的
方法,进而提示系统繁忙。
然后在主启动类中激活,添加注解@EnableCircuitBreaker
package com.badao.springclouddemo; 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); } }
此时再和上面一样进行测试
为了验证上面的机制,将方法延迟的时间和接受的最大时间修改下
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") }) public String paymentInfo_TimeOut(Integer id) { //int age = 10/0; try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): "; } public String paymentInfo_TimeOutHandler(Integer id) { return "线程池: "+Thread.currentThread().getName()+" 8001系统繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"o(╥﹏╥)o"; }
此时再请求会发现会返回正常的接口提示
12、服务消费者80端实现服务降级
首先yml配置文件中开启配置
feign: hystrix: enabled: true
完整配置文件
server: port: 88 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/ #设置feign客户端超时时间(OpenFeign默认支持ribbon) ribbon: #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 ReadTimeout: 3500 #指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 3500 feign: hystrix: enabled: true
然后主启动类中添加@EnableHystrix注解
package com.badao.springclouddemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients @EnableHystrix public class OrderHystrixMain88 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain88.class,args); } }
修改Controller
@GetMapping("/consumer/payment/hystrix/timeout/{id}") @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="2000") }) public String paymentInfo_TimeOut(Integer id) { //int age = 10/0; String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } public String paymentInfo_TimeOutHandler(Integer id) { return "线程池: "+Thread.currentThread().getName()+"来自消费者88的提示:服务提供者繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"o(╥﹏╥)o"; }
同服务提供者降级一样,这里也是通过添加注解配置fallback的兜底方法并且设置超时响应的最大等待时间为2秒,而服务提供者的
响应时间为3秒,所以这样必然会触发降级进入fallback。