Hystrix
1.分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败.
1.1 服务雪崩
- 多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C有嗲用其他的微服务,这就是所谓的"扇出".如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,也就是所谓的"雪崩效应".
- 对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和.比失败更糟糕的是,这些应用程序还可能导致服务之间延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障.这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统.所以,通常你会发现一个模块下的某个实例十八后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用的其他的模块,这样就会发生级联故障,或者叫雪崩.
2. Hystrix是什么
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性.
"断路器"本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩.
3. Hystrix能干嘛
3.1 服务降级(fallBack)
- 服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallBack
- 哪些情况会触发降级
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
3.2 服务熔断(break)
- 类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
- 就是保险丝
3.3 服务限流(flowlimit)
- 秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行.
3.4 高并发测试
-
使用JMeter进行20000个高并发测试
-
为什么timeout测试,ok接口会变慢
因为tomcat的默认工作线程数被打满了,没有多余的线程来分解压力和处理
-
结论:上面还是服务提供者8001自己测试,计入此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
3.5 故障现象和导致原因
- 8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程数被打满了,没有多余的线程来分解压力和处理
- 80此时调用8001,客户端访问响应缓慢,转圈圈
3.6 上述结论
- 正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生
3.7 如何解决?解决的要求
- 超时导致服务器变慢(转圈) --------> 超时不再等待
- 出错(宕机或者程序运行出错) ------> 出错要有兜底
- 解决
- 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
- 对方服务(8001)宕机了,调用者(80)不能一直卡死等待,必须有服务降级
- 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级.
4. 服务降级
4.1 降级配置
- @HystrixCommand
4.2 8001先从自身找问题
- 设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback
4.3 8001 fallback
-
业务类启用
//使用HystrixCommand注解,超时就调用paymentInfoTimeOutHandle方法, //毫秒超时设置 execution.isolation.thread.timeoutInMilliseconds //1.超时服务降级会走paymentInfoTimeOutHandle方法 //2.运行时错误服务降级会走paymentInfoTimeOutHandle方法 @HystrixCommand(fallbackMethod = "paymentInfoTimeOutHandle",commandProperties = { @HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value="3000" ) } ) public String paymentInfoTimeOut() { int timeNumber = 5; // int age = 10/0 try { TimeUnit.SECONDS.sleep(timeNumber); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池:" + Thread.currentThread().getName() + "paymentInfoTimeOut,耗时(秒):" + timeNumber; } @Override public String paymentInfoTimeOutHandle() { return "线程池:" + Thread.currentThread().getName() + "paymentInfoTimeOutHandle,系统繁忙,请稍后再试"; }
-
主启动类激活
package com.atguigu.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启用 @EnableCircuitBreaker public class HystrixPaymentMain8001 { public static void main(String[] args) { SpringApplication.run(HystrixPaymentMain8001.class,args); } }
4.3 目前问题
- 每个业务方法对应一个兜底的方法,代码膨胀
- 同一个自定义的分开
4.4 解决问题
-
在类上面加@DefaultProperties(defaultFallback = “”)
- 1 : 1每个方法配置一个服务降级方法,技术上可以,实际上傻X
- 1 : N除了个别重要核心业务有专属,其他普通的可以通过@DefaultProperties(defaultFallback = “”)统一跳转到统一处理结果页面
- 通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量
-
服务端宕机情况下fallback方法
package com.atguigu.springcloud.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; @Component //OrderPaymentFallBackService实现OrderPaymentService接口 @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = OrderPaymentFallBackService.class) public interface OrderPaymentService { @GetMapping("/payment/ok") String paymentInfoOk(); @GetMapping("/payment/timeout") String paymentInfoTimeOut(); }
package com.atguigu.springcloud.service; import org.springframework.stereotype.Component; @Component public class OrderPaymentFallBackService implements OrderPaymentService { @Override public String paymentInfoOk() { return "OrderPaymentFallBackService fall back -- paymentInfoOk"; } @Override public String paymentInfoTimeOut() { return "OrderPaymentFallBackService fall back -- paymentInfoTimeOut"; } }
服务端宕机,不用再去尝试请求,直接fallback
5. 服务熔断
5.1 断路器
- 断路器 -----> 一句话就是家里的保险丝
5.2 熔断是什么
-
熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制.当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息.
当检测到该节点微服务调用响应正常后,恢复调用链路
在Spring Cloud框架里,熔断机制通过Hystrix实现.Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制.熔断机制的注解是@HystrixCommand.
5.3 实操
-
service代码
package com.atguigu.springcloud.service.impl; import cn.hutool.core.util.IdUtil; import com.atguigu.springcloud.service.HystrixPaymentService; 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 HystrixPaymentServiceImpl implements HystrixPaymentService { // **********************服务熔断********************************* //开启断路器,在10秒窗口期请求必须大于等于10次,并且失败率超过60%才会跳闸; // 如果该hystrix命令的调用次数不足10次,即使所有的请求都超时或其他原因失败,断路器都不会打开 @Override @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(Integer id) { if ( id < 0 ){ throw new RuntimeException("****** id 不能为负数"); } String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber; } @Override public String paymentCircuitBreaker_fallback(Integer id) { return "****** id 不能为负数"; } }
-
controller类代码
package com.atguigu.springcloud.controller; import com.atguigu.springcloud.service.HystrixPaymentService; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; 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.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Slf4j @RequestMapping("/payment") public class HystrixPaymentController { @Resource private HystrixPaymentService service; // *****************服务熔断***************************** @GetMapping("/breaker/{id}") public String paymentCircuitBreaker(@PathVariable("id") Integer id){ String result = service.paymentCircuitBreaker(id); log.info(result); return result; } }
5.4 总结
5.4.1 熔断类型
-
熔断打开
请求不再进行调用当前服务,内部设置始终一般为MTTR (平均故障处理时间),当打开时长达到所设时钟,则进入半熔断状态.
-
熔断关闭
熔断关闭不会对服务进行熔断
-
熔断半开
部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前恢复正常,关闭熔断.
5.4.2 测试结论
- 多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行
- 服务降级–>服务熔断—>缓慢恢复服务链路
5.4.3 断路器在什么情况下开始起作用
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NNjkk76p-1608112453180)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201215174245301.png)]
设计到断路器的三个重要参数: 快照时间窗,请求总数阈值,错误百分比阈值
- 快照时间窗: 断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒.
- 请求总数阈值:在快照时间窗内,必须满足请求总数阈值才有资格熔断.默认为20,意味着10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开.
- 错误百分比阈值:当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,如果在这30次调用中,有20次发生了超时异常,也就是超过60%的错误百分比,在默认设定60%阈值情况下,这时候就会将断路器打开.
6. Hystrix图形化DashBoard监控
6.1 Hystrix图形化DashBoard搭建
-
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.atguigu.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-consumer-hystrix-dashboard9001</artifactId> <dependencies> <!--dashboard依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> </dependency> </dependencies> </project>
-
主启动类
package com.atguigu.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; @SpringBootApplication @EnableHystrixDashboard public class HystrixDashBoardMain9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashBoardMain9001.class,args); } }
-
application.yml文件
server: port: 9001
6.2 Hystrix图形化DashBoard监控实战
-
配置bean,不然会报(Unable to connect to Command Metric Stream.错误)
/** * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑 * ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream", * 只要在自己的项目里面配置上下面的servlet就可以了. * */ @Bean public ServletRegistrationBean getServlet(){ HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; }
-
图像化监控界面