南通wap网站建设,c 网站开发类似优酷,专门app软件制作费用,WordPress搭建连不上数据库一、前言 接下来是开展一系列的 SpringCloud 的学习之旅#xff0c;从传统的模块之间调用#xff0c;一步步的升级为 SpringCloud 模块之间的调用#xff0c;此篇文章为第五篇#xff0c;即介绍 Hystrix 断路器。
二、概述
2.1 分布式系统面临的问题 复杂分布式体系结构中…一、前言 接下来是开展一系列的 SpringCloud 的学习之旅从传统的模块之间调用一步步的升级为 SpringCloud 模块之间的调用此篇文章为第五篇即介绍 Hystrix 断路器。
二、概述
2.1 分布式系统面临的问题 复杂分布式体系结构中的应用程序有数十个依赖关系每个依赖关系在某些时候将不可避免地失败。 下图中的请求需要调用 A、P、H、I 四个服务如果一切顺利则没有什么问题关键是如果 I 服务超时会出现什么情况呢 会出现服务雪崩的现象。多个微服务之间调用的时候假设微服务 A 调用微服务 B 和微服务 C微服务 B 和微服务 C 又调用其它的微服务这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用对微服务 A 的调用就会占用越来越多的系统资源进而引起系统崩溃所谓的“雪崩效应” 。 对于高流量的应用来说单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是这些应用程序还可能导致服务之间的延迟增加备份队列线程和其他系统资源紧张导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理以便单个依赖关系的失败不能取消整个应用程序或系统。 所以通常当你发现一个模块下的某个实例失败后这时候这个模块依然还会接收流量然后这个有问题的模块还调用了其他的模块这样就会发生级联故障或者叫雪崩。
2.2 Hystrix 是什么 Hystrix 是一个用于处理分布式系统的延迟和容错的开源库在分布式系统里许多依赖不可避免的会调用失败比如超时、异常等Hystrix 能够保证在一个依赖出问题的情况下不会导致整体服务失败避免级联故障以提高分布式系统的弹性。 “断路器”本身是一种开关装置当某个服务单元发生故障之后通过断路器的故障监控类似熔断保险丝向调用方返回一个符合预期的、可处理的备选响应FallBack而不是长时间的等待或者抛出调用方无法处理的异常这样就保证了服务调用方的线程不会被长时间、不必要地占用从而避免了故障在分布式系统中的蔓延乃至雪崩。
2.3 Hystrix 用途 1、服务降级 2、服务熔断 3、接近实时的监控
2.4 现状 目前 Hystrix 也已经进入了维护期。如下图 三、Hystrix 重要概念
3.1 服务降级 当客户端迟迟得不到服务端的响应时给客户端提示一个 “服务器忙请稍后再试” 类似的响应不让客户端等待并立刻返回一个友好提示。 哪些情况会触发降级程序运行异常、超时、服务熔断触发服务降级、线程池/信号量打满等也会触发服务降级。
3.2 服务熔断 和家里以前用的保险丝类似当服务器达到最大服务访问后直接拒绝访问拉闸限电然后调用服务降级的方法返回友好提示。 服务降级 -- 进而熔断 -- 恢复链路调用。
3.3 服务限流 当出现类似于秒杀等高并发操作严禁一窝蜂的过来拥挤大家排队一秒钟 N 个有序进行。
四、Hystrix 案例
4.1 构建 创建一个子模块工程 cloud-provider-hystrix-payment8001pom.xml 内容如下
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdcom.springcloud/groupIdartifactIdSpringCloud/artifactIdversion1.0-SNAPSHOT/version/parentartifactIdcloud-provider-hystrix-payment8001/artifactIdpropertiesmaven.compiler.source8/maven.compiler.sourcemaven.compiler.target8/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/propertiesdependencies!--hystrix--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-hystrix/artifactId/dependency!--eureka client--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependency!--web--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependencydependency!-- 引入自己定义的api通用包可以使用Payment支付Entity --groupIdcom.springcloud/groupIdartifactIdcloud-api-commons/artifactIdversion${project.version}/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies/project 为了方便后续的测试我们这边先不采用集群的方式进行配置只配置一台服务进行注册application.yml 内容如下所示
server:port: 8001spring:application:name: cloud-provider-hystrix-paymenteureka:client:register-with-eureka: truefetch-registry: trueservice-url:# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eurekadefaultZone: http://eureka7001.com:7001/eureka 主启动类的代码如下所示
package com.springcloud;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;SpringBootApplication
EnableEurekaClient //本服务启动后会自动注册进eureka服务中
public class PaymentHystrixMain8001
{public static void main(String[] args){SpringApplication.run(PaymentHystrixMain8001.class,args);}
} 业务类 service 层代码如下所示
Service
public class PaymentService
{/*** 正常访问一切OK*/public String paymentInfo_OK(Integer id){return 线程池:Thread.currentThread().getName()paymentInfo_OK,id: id\tO(∩_∩)O;}/*** 超时访问演示降级*/public String paymentInfo_TimeOut(Integer id){try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }return 线程池:Thread.currentThread().getName()paymentInfo_TimeOut,id: id\tO(∩_∩)O耗费3秒;}
} 控制层 controller 代码如下所示
RestController
Slf4j
public class PaymentController
{Autowiredprivate 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) throws InterruptedException{String result paymentService.paymentInfo_TimeOut(id);log.info(****result: result);return result;}
} 分别启动 cloud-eureka-server7001 和 cloud-provider-hystrix-payment8001 模块进行方法测试访问 http://localhost:8001/payment/hystrix/ok/31效果如下 当问 http://localhost:8001/payment/hystrix/timeout/31效果如下 可以看到这两个方法都可以正常的访问。
4.2 高并发测试
4.2.1 模块自测 上面的两个方法在非高并发的情形下访问时没有任何问题的接下来我们测试下在高并发的场景下方法还是否可以正常访问。 启动 jemeter如果你没有用过这个压测软件请参考安装和使用的这两篇文章创建 2000 个并发请求对 cloud-provider-hystrix-payment8001 模块进行压测使这 2000 个请求都去访问 paymentInfo_TimeOut 服务如下图 然后再来一个请求去访问 paymentInfo_OK如下图 可以看到这两个请求都在转圈圈都得等待一会才能够响应这是因为 tomcat 的默认的工作线程数被打满了没有多余的线程来分解压力和处理。 上面还是服务提供者 8001 自己测试假如此时外部的消费者 80 也来访问那消费者只能干等最终导致消费端 80 不满意服务端 8001 直接被拖死。接下来我们验证下。
4.2.2 模块公测 新建一个订单模块 cloud-consumer-feign-hystrix-order80pom.xml 内容如下所示
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdcom.springcloud/groupIdartifactIdSpringCloud/artifactIdversion1.0-SNAPSHOT/version/parentartifactIdcloud-consumer-feign-hystrix-order80/artifactIdpropertiesmaven.compiler.source8/maven.compiler.sourcemaven.compiler.target8/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/propertiesdependencies!--openfeign--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependency!--hystrix--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-hystrix/artifactId/dependency!--eureka client--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-eureka-client/artifactId/dependency!-- 引入自己定义的api通用包可以使用Payment支付Entity --dependencygroupIdcom.springcloud/groupIdartifactIdcloud-api-commons/artifactIdversion${project.version}/version/dependency!--web--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency!--一般基础通用配置--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies
/project application.yml 的内容如下所示
server:port: 80eureka:client:register-with-eureka: falseservice-url:defaultZone: http://eureka7001.com:7001/eureka/ 主启动类的代码如下所示
SpringBootApplication
EnableFeignClients
public class OrderHystrixMain80
{public static void main(String[] args){SpringApplication.run(OrderHystrixMain80.class,args);}
} service 层代码如下所示
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);
} controller 层代码如下所示
RestController
Slf4j
public class OrderHystirxController {Resourceprivate 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;}
} 启动 cloud-consumer-feign-hystrix-order80 模块进行测试输入http://localhost/consumer/payment/hystrix/ok/31如下可以感觉到响应很快。 接下来进行高并发测试启动 jmeter 使用 20000 个线程压测 cloud-provider-hystrix-payment8001 模块并让 order80 微服务再去访问正常的 Ok 微服务 8001 地址http://localhost/consumer/payment/hystrix/ok/32如下 如上图可以看到order80 要么转圈圈等待要么消费端报超时错误。
4.3 故障现象和原因 cloud-provider-hystrix-payment8001 模块的同一层次的其它接口服务被困死因为 tomcat 线程池里面的工作线程已经被挤占完毕order80 此时调用 8001客户端访问响应缓慢转圈圈。
4.4 上述结论 正因为有上述故障或不佳表现才有我们的降级/容错/限流等技术诞生。
4.5 解决方案 1、若超时导致服务器变慢出现转圈现象则超时不再等待。 2、若出现宕机或者程序运行出错则要有兜底的方案。 3、若对方服务 (8001) 超时了调用者order80不能一直卡死等待必须有服务降级。 4、若对方服务 (8001) down 机了调用者 (80) 不能一直卡死等待必须有服务降级。 5、若对方服务 (8001) OK调用者 (80) 自己出故障或有自我要求自己的等待时间小于服务提供者自己处理降级。
4.6 服务降级 首先从 cloud-provider-hystrix-payment8001 模块自身找问题设置自身调用超时时间的峰值峰值内可以正常运行超过了需要有兜底的方法处理作服务降级 fallback。
4.6.1 服务端降级 首先修改 cloud-provider-hystrix-payment8001 模块的 PaymentHystrixService 类代码如下
Service
public class PaymentService
{/*** 正常访问一切OK*/public String paymentInfo_OK(Integer id){return 线程池:Thread.currentThread().getName()paymentInfo_OK,id: id\tO(∩_∩)O;}/*** 超时访问演示降级*/HystrixCommand(fallbackMethod paymentInfo_TimeOutHandler,commandProperties {HystrixProperty(nameexecution.isolation.thread.timeoutInMilliseconds,value5000)})public String paymentInfo_TimeOut(Integer id){try { TimeUnit.SECONDS.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); }return 线程池:Thread.currentThread().getName()paymentInfo_TimeOut,id: id\tO(∩_∩)O耗费3秒;}public String paymentInfo_TimeOutHandler(Integer id){return /(ㄒoㄒ)/调用支付接口超时或异常\t \t当前线程池名字 Thread.currentThread().getName();}
} 一旦调用服务方法失败并抛出了错误信息后会自动调用 HystrixCommand 标注好的 fallbackMethod 调用类中的指定方法。 在主启动类上添加 EnableCircuitBreaker 注解如下图
SpringBootApplication
EnableEurekaClient //本服务启动后会自动注册进eureka服务中
EnableCircuitBreaker
public class PaymentHystrixMain8001
{public static void main(String[] args){SpringApplication.run(PaymentHystrixMain8001.class,args);}
} 启动模块进行测试由于我们配置的是超时时间为 5s而程序需要处理 7s程序调用时应该会调用我们指定的方法如下图 继续修改下 PaymentHystrixService 类的代码演示下当程序报错时是否会调用我们指定的方法如下
Service
public class PaymentService
{/*** 正常访问一切OK*/public String paymentInfo_OK(Integer id){return 线程池:Thread.currentThread().getName()paymentInfo_OK,id: id\tO(∩_∩)O;}/*** 超时访问演示降级*/HystrixCommand(fallbackMethod paymentInfo_TimeOutHandler,commandProperties {HystrixProperty(nameexecution.isolation.thread.timeoutInMilliseconds,value5000)})public String paymentInfo_TimeOut(Integer id){int a 10/0;// try { TimeUnit.SECONDS.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); }return 线程池:Thread.currentThread().getName()paymentInfo_TimeOut,id: id\tO(∩_∩)O耗费3秒;}public String paymentInfo_TimeOutHandler(Integer id){return /(ㄒoㄒ)/调用支付接口超时或异常\t \t当前线程池名字 Thread.currentThread().getName();}
} 可以看到当程序发生错误时也会进入我们指定的方法 总结当前服务超时或内部错误时都做服务降级兜底的方案都是 paymentInfo_TimeOutHandler 4.6.2 客户端降级 修改 cloud-consumer-fiegn-hystrix-order80 模块的 application.yml内容如下
server:port: 80eureka:client:register-with-eureka: falseservice-url:defaultZone: http://eureka7001.com:7001/eureka/feign:hystrix:enabled: true 在主启动类上添加 EnableHystrix 注解代码如下
SpringBootApplication
EnableFeignClients
EnableHystrix
public class OrderHystrixMain80
{public static void main(String[] args){SpringApplication.run(OrderHystrixMain80.class,args);}
}修改 OrderHystirxController 类代码如下
RestController
Slf4j
public class OrderHystirxController {Resourceprivate 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})HystrixCommand(fallbackMethod paymentTimeOutFallbackMethod,commandProperties {HystrixProperty(nameexecution.isolation.thread.timeoutInMilliseconds,value1500)})public String paymentInfo_TimeOut(PathVariable(id) Integer id){String result paymentHystrixService.paymentInfo_TimeOut(id);return result;}public String paymentTimeOutFallbackMethod(PathVariable(id) Integer id){return 我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o;}
} 启动 cloud-consumer-fiegn-hystrix-order80 模块进行测试如下可以看到客户端的服务降级也成功了。 4.6.3 目前问题 1、每个业务方法都需要写一个兜底的方法代码膨胀。 2、业务代码和兜底方法放在一起混乱不堪。
4.6.4 配置全局方法 为了解决每个业务方法都需要写一个兜底的方法代码膨胀的问题可以采取配置全局服务降级的方法。 此种方法可以解决出现运行时异常和超时异常的服务降级。 修改 cloud-consumer-fiegn-hystrix-order80 模块的 OrderHystirxController 类的代码如下所示
RestController
Slf4j
DefaultProperties(defaultFallback payment_Global_FallbackMethod)
public class OrderHystirxController {Resourceprivate 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})// 加了DefaultProperties属性注解并且没有写具体方法名字就用统一全局的HystrixCommandpublic String paymentInfo_TimeOut(PathVariable(id) Integer id){int a 10/0;String result paymentHystrixService.paymentInfo_TimeOut(id);return result;}public String paymentTimeOutFallbackMethod(PathVariable(id) Integer id){return 我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o;}public String payment_Global_FallbackMethod(){return Global异常处理信息请稍后再试/(ㄒoㄒ)/~~;}
} 通过添加 DefaultProperties(defaultFallback ) 注解统一跳转到统一处理结果页面 如果有个别重要核心业务有专属配套方法只需要在该方法上继续使用 HystrixCommand 注解并指定拖地方法即可。 通用的和独享的各自分开避免了代码膨胀合理减少了代码量。
4.6.5 解决服务端宕机 为了解决业务代码和兜底方法放在一起混乱不堪的问题。也是为了实现当服务器宕机或关闭时的服务降级。 本次案例服务降级处理是在客户端 order80 实现完成的与服务端 8001 没有关系只需要为 Feign 客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。 未来我们要面对的异常包括运行时异常、超时异常和宕机异常。 修改 cloud-consumer-feign-hystrix-order80 模块根据 cloud-consumer-feign-hystrix-order80 已经有的 PaymentHystrixService 接口重新新建一个类 PaymentFallbackService 实现该接口统一为接口里面的方法进行异常处理代码如下
Component
public class PaymentFallbackService implements PaymentHystrixService{Overridepublic String paymentInfo_OK(Integer id) {return ------- PaymentFallbackService back-paymentInfo_OK fall;}Overridepublic String paymentInfo_TimeOut(Integer id) {return ------- PaymentFallbackService back-paymentInfo_TimeOut fall;}
} 修改 PaymentFeignClientService 类的代码添加 fallback代码如下
Component
FeignClient(value CLOUD-PROVIDER-HYSTRIX-PAYMENT,fallback PaymentFallbackService.class)
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);} 分别启动 cloud-eureka-server7001、 cloud-provider-hystrix-payment8001 和 cloud-consumer-feign-hystrix-order80 模块输入 http://localhost/consumer/payment/hystrix/ok/32进行测试如下图 此时故意关闭 cloud-provider-hystrix-payment8001 模块再次进行访问如下图 此时服务端 provider 已经 down 了但是我们做了服务降级处理让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。
4.7 服务熔断
4.7.1 熔断机制概述 熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时会进行服务的降级进而熔断该节点微服务的调用快速返回错误的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。 在 SpringCloud 框架里熔断机制通过 Hystrix 实现。Hystrix 会监控微服务间调用的状况当失败的调用到一定阈值缺省是 5 秒内 20 次调用失败就会启动熔断机制。熔断机制的注解是 HystrixCommand。
4.7.2 熔断实操 修改 cloud-provider-hystrix-payment8001 模块的 PaymentService 类添加如下的代码 // 服务熔断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ㄒ)/~~ id: id;} 上面 HystrixCommand 注解里面的 commandProperties 配置的属性的意思是假设在 10s时间窗口期 的时间10请求次数 次请求里面有 6 失败率次是失败调用了兜底的方法的那么我们的断路器就会发生作用。 那么为什么要配置这些参数呢首先看下官网下的这张图片一个断路器的打开和关闭是按照以下图片的这五个过程。 1、请求的次数是否达到了峰值的次数。 2、错误次数的百分比是否达到了阈值。 3、断路器的状态将从关闭状态转换为开启状态开启状态就是跳闸了用不了了。正常情况为关闭状态 4、当断路器处于开启的状态后一段时间之内所有的请求都将无法使用。 5、等到时间窗口期一过那么下一个请求尝试着让它通过一下这个就是所谓的半开状态如果这个请求还是无法通过就说明断路器还是处于开启状态如果这个请求通过了说明服务已经恢复了那么就将断路器恢复成关闭状态再回到第一步循环反复。 修改 PaymentController 类添加如下的代码 GetMapping(/payment/circuit/{id})public String paymentCircuitBreaker(PathVariable(id) Integer id){String result paymentService.paymentCircuitBreaker(id);log.info(****result: result);return result;} 启动 cloud-provider-hystrix-payment8001 模块进行自测首先测试正常调用的情况 http://localhost:8001/payment/circuit/31如下图 然后测试异常调用的情况 http://localhost:8001/payment/circuit/-31如下图 接下来进行重点测试多次异常调用然后点击正常调用会出现正常调用也出现异常等到一段时间之后又自己恢复了如下图 4.7.3 熔断原理 先看下大神的结论如下图 1、断路器一开始处于断开状态当失败次数增多时断路器变成开启状态。 2、等到一段时间之后断路器处于半开状态尝试的去处理请求。 3、当处理成功之后断路器再次变成断开状态。 4、当处理失败之后断路器再次变成开启状态
4.7.4 熔断类型 由以上分析可以得出结论熔断类型分为三种熔断打开、熔断关闭和熔断半开。 1、熔断打开请求不再进行调用当前服务内部设置时钟一般为 MTTR平均故障处理时间)当打开时长达到所设时钟则进入半熔断状态。 2、熔断关闭熔断关闭不会对服务进行熔断 3、熔断半开部分请求根据规则调用当前服务如果请求成功且符合规则则认为当前服务恢复正常关闭熔断。
4.7.5 官网断路器流程图 官网的步骤就是我们上面分析的那张图如下 4.7.6 断路器何时起作用 断路器在什么情况下开始起作用涉及到断路器的三个重要参数快照时间窗、请求总数阀值、错误百分比阀值。 1、快照时间窗断路器确定是否打开需要统计一些请求和错误数据而统计的时间范围就是快照时间窗默认为最近的 10 秒。 2、请求总数阀值在快照时间窗内必须满足请求总数阀值才有资格熔断。默认为 20意味着在 10 秒内如果该 hystrix 命令的调用次数不足 20 次即使所有的请求都超时或其他原因失败断路器都不会打开。 3、错误百分比阀值当请求总数在快照时间窗内超过了阀值比如发生了 30 次调用如果在这 30 次调用中有 15 次发生了超时异常也就是超过 50% 的错误百分比在默认设定 50% 阀值情况下这时候就会将断路器打开。
4.7.7 断路器开启或者关闭的条件 1、当满足一定的阀值的时候默认10 秒内超过 20 个请求次数 2、当失败率达到一定的时候默认 10 秒内超过 50% 的请求失败 3、到达以上阀值断路器将会开启。 4、当开启的时候所有请求都不会进行转发 5、一段时间之后默认是 5 秒这个时候断路器是半开状态会让其中一个请求进行转发。如果成功断路器会关闭若失败继续开启。重复 4 和 5
4.7.8 断路器开启之后 再有请求调用的时候将不会调用主逻辑而是直接调用降级 fallback。通过断路器实现了自动地发现错误并将降级逻辑切换为主逻辑减少响应延迟的效果。 原来的主逻辑要如何恢复呢对于这一问题hystrix 也为我们实现了自动恢复功能。当断路器打开对主逻辑进行熔断之后hystrix 会启动一个休眠时间窗在这个时间窗内降级逻辑是临时的成为主逻辑当休眠时间窗到期断路器将进入半开状态释放一次请求到原来的主逻辑上如果此次请求正常返回那么断路器将继续闭合主逻辑恢复如果这次请求依然有问题断路器继续进入打开状态休眠时间窗重新计时。
4.7.9 Properties 属性总结 HystrixCommand 注解的 commandProperties 可能用到的所有属性如下所示
//All
HystrixCommand(fallbackMethod str_fallbackMethod,groupKey strGroupCommand,commandKey strCommand,threadPoolKey strThreadPool,commandProperties {// 设置隔离策略THREAD 表示线程池 SEMAPHORE信号池隔离HystrixProperty(name execution.isolation.strategy, value THREAD),// 当隔离策略选择信号池隔离的时候用来设置信号池的大小最大并发数HystrixProperty(name execution.isolation.semaphore.maxConcurrentRequests, value 10),// 配置命令执行的超时时间HystrixProperty(name execution.isolation.thread.timeoutinMilliseconds, value 10),// 是否启用超时时间HystrixProperty(name execution.timeout.enabled, value true),// 执行超时的时候是否中断HystrixProperty(name execution.isolation.thread.interruptOnTimeout, value true),// 执行被取消的时候是否中断HystrixProperty(name execution.isolation.thread.interruptOnCancel, value true),// 允许回调方法执行的最大并发数HystrixProperty(name fallback.isolation.semaphore.maxConcurrentRequests, value 10),// 服务降级是否启用是否执行回调函数HystrixProperty(name fallback.enabled, value true),// 是否启用断路器HystrixProperty(name circuitBreaker.enabled, value true),// 该属性用来设置在滚动时间窗中断路器熔断的最小请求数。例如默认该值为 20 的时候// 如果滚动时间窗默认10秒内仅收到了19个请求 即使这19个请求都失败了断路器也不会打开。HystrixProperty(name circuitBreaker.requestVolumeThreshold, value 20),// 该属性用来设置在滚动时间窗中表示在滚动时间窗中在请求数量超过// circuitBreaker.requestVolumeThreshold 的情况下如果错误请求数的百分比超过50,// 就把断路器设置为 打开 状态否则就设置为 关闭 状态。HystrixProperty(name circuitBreaker.errorThresholdPercentage, value 50),// 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后// 会将断路器置为 半开 状态尝试熔断的请求命令如果依然失败就将断路器继续设置为 打开 状态// 如果成功就设置为 关闭 状态。HystrixProperty(name circuitBreaker.sleepWindowinMilliseconds, value 5000),// 断路器强制打开HystrixProperty(name circuitBreaker.forceOpen, value false),// 断路器强制关闭HystrixProperty(name circuitBreaker.forceClosed, value false),// 滚动时间窗设置该时间用于断路器判断健康度时需要收集信息的持续时间HystrixProperty(name metrics.rollingStats.timeinMilliseconds, value 10000),// 该属性用来设置滚动时间窗统计指标信息时划分桶的数量断路器在收集指标信息的时候会根据// 设置的时间窗长度拆分成多个 桶 来累计各度量值每个桶记录了一段时间内的采集指标。// 比如 10 秒内拆分成 10 个桶收集这样所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常HystrixProperty(name metrics.rollingStats.numBuckets, value 10),// 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。HystrixProperty(name metrics.rollingPercentile.enabled, value false),// 该属性用来设置百分位统计的滚动窗口的持续时间单位为毫秒。HystrixProperty(name metrics.rollingPercentile.timeInMilliseconds, value 60000),// 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。HystrixProperty(name metrics.rollingPercentile.numBuckets, value 60000),// 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数// 就从最初的位置开始重写。例如将该值设置为100, 滚动窗口为10秒若在10秒内一个 “桶 ”中发生了500次执行// 那么该 “桶” 中只保留 最后的100次执行的统计。另外增加该值的大小将会增加内存量的消耗并增加排序百分位数所需的计算时间。HystrixProperty(name metrics.rollingPercentile.bucketSize, value 100),// 该属性用来设置采集影响断路器状态的健康快照请求的成功、 错误百分比的间隔等待时间。HystrixProperty(name metrics.healthSnapshot.intervalinMilliseconds, value 500),// 是否开启请求缓存HystrixProperty(name requestCache.enabled, value true),// HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中HystrixProperty(name requestLog.enabled, value true),},threadPoolProperties {// 该参数用来设置执行命令线程池的核心线程数该值也就是命令执行的最大并发量HystrixProperty(name coreSize, value 10),// 该参数用来设置线程池的最大队列大小。当设置为 -1 时线程池将使用 SynchronousQueue 实现的队列// 否则将使用 LinkedBlockingQueue 实现的队列。HystrixProperty(name maxQueueSize, value -1),// 该参数用来为队列设置拒绝阈值。 通过该参数 即使队列没有达到最大值也能拒绝请求。// 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue// 队列不能动态修改它的对象大小而通过该属性就可以调整拒绝请求的队列大小了。HystrixProperty(name queueSizeRejectionThreshold, value 5),}
)
public String strConsumer() {return hello 2020;
}
public String str_fallbackMethod()
{return *****fall back str_fallbackMethod;
}
4.8 服务限流 后面会详细讲解 alibaba 的 Sentinel 这边就不展开讲解了。
五、Hystrix 工作流程
5.1 官网图例 5.2 步骤说明 1、创建 HystrixCommand用在依赖的服务返回单个操作结果的时候 或 HystrixObserableCommand用在依赖的服务返回多个操作结果的时候 对象。 2、命令执行。其中 HystrixComand 实现了下面前两种执行方式而 HystrixObservableCommand 实现了后两种执行方式execute()同步执行从依赖的服务返回一个单一的结果对象 或是在发生错误的时候抛出异常。queue()异步执行 直接返回 一个Future对象 其中包含了服务执行结束时要返回的单一结果对象。observe()返回 Observable 对象它代表了操作的多个结果它是一个 Hot Obserable不论 事件源 是否有 订阅者都会在创建后对事件进行发布所以对于 Hot Observable 的每一个 订阅者 都有可能是从 事件源 的中途开始的并可能只是看到了整个操作的局部过程。toObservable() 同样会返回 Observable 对象也代表了操作的多个结果但它返回的是一个 Cold Observable没有 订阅者 的时候并不会发布事件而是进行等待直到有 订阅者 之后才发布事件所以对于 Cold Observable 的订阅者它可以保证从一开始看到整个操作的全部过程。 3、若当前命令的请求缓存功能是被启用的 并且该命令缓存命中 那么缓存的结果会立即以 Observable 对象的形式 返回。 4、检查断路器是否为打开状态。如果断路器是打开的那么 Hystrix 不会执行命令而是转接到 fallback 处理逻辑第 8 步如果断路器是关闭的检查是否有可用资源来执行命令第 5 步。 5、线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列或者信号量不使用线程池的时候已经被占满 那么 Hystrix 也不会执行命令 而是转接到 fallback 处理逻辑第8步。 6、Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。HystrixCommand.run() 返回一个单一的结果或者抛出异常。HystrixObservableCommand.construct() 返回一个Observable 对象来发射多个结果或通过 onError 发送错误通知。 7、Hystrix 会将 成功、失败、拒绝、超时 等信息报告给断路器 而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开来对某个依赖服务的请求进行 熔断/短路。 8、当命令执行失败的时候 Hystrix 会进入 fallback 尝试回退处理 我们通常也称该操作为 服务降级。而能够引起服务降级处理的情况有下面几种第4步 当前命令处于熔断/短路状态断路器是打开的时候。第 5 步 当前命令的线程池、 请求队列或 者信号量被占满的时候。第 6 步HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候。 9、当 Hystrix 命令执行成功之后 它会将处理结果直接返回或是以 Observable 的形式返回。
注意 如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常 Hystrix 依然会返回一个 Observable 对象 但是它不会发射任何结果数据 而是通过 onError 方法通知命令立即中断请求并通过 onError() 方法将引起命令失败的异常发送给调用者。
六、服务监控 hystrixDashboard
6.1 概述 除了隔离依赖服务的调用以外Hystrix 还提供了准实时的调用监控Hystrix Dashboard Hystrix 会持续地记录所有通过 Hystrix 发起的请求的执行信息并以统计报表和图形的形式展示给用户包括每秒执行多少请求多少成功多少失败等。Netflix 通过 hystrix-metrics-event-stream 项目实现了对以上指标的监控。Spring Cloud 也提供了 Hystrix Dashboard 的整合对监控内容转化成可视化界面。
6.2 搭建仪表盘 新建一个 cloud-consumer-hystrix-dashboard9001 子模块pom.xml 内容如下所示
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdcom.springcloud/groupIdartifactIdSpringCloud/artifactIdversion1.0-SNAPSHOT/version/parentartifactIdcloud-consumer-hystrix-dashboard9001/artifactIdpropertiesmaven.compiler.source8/maven.compiler.sourcemaven.compiler.target8/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/propertiesdependenciesdependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-hystrix-dashboard/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies/project application.yml 内容如下所示 server:port: 9001 启动类的代码如下所示注意这块新加一个 EnableHystrixDashboard 注解
SpringBootApplication
EnableHystrixDashboard
public class HystrixDashboardMain9001
{public static void main(String[] args){SpringApplication.run(HystrixDashboardMain9001.class,args);}
} 针对于所有的 Provider 微服务提供类8001/8002/8003都需要监控依赖配置即需要确保 pom.xml 中存在以下的注解
!-- actuator监控信息完善 --
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId
/dependency 启动 cloud-consumer-hystrix-dashboard9001 模块并输入 http://localhost:9001/hystrix 进行测试如下图 6.3 服务监控演示 修改 cloud-provider-hystrix-payment8001 的启动类因为新版本 Hystrix 需要在主启动类 MainAppHystrix8001 中指定监控路径代码如下
SpringBootApplication
EnableEurekaClient //本服务启动后会自动注册进eureka服务中
EnableCircuitBreaker
public class PaymentHystrixMain8001
{public static void main(String[] args){SpringApplication.run(PaymentHystrixMain8001.class,args);}/***此配置是为了服务监控而配置与服务容错本身无关springcloud升级后的坑*ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream*只要在自己的项目里配置上下面的servlet就可以了*/Beanpublic ServletRegistrationBean getServlet() {HystrixMetricsStreamServlet streamServlet new HystrixMetricsStreamServlet();ServletRegistrationBean registrationBean new ServletRegistrationBean(streamServlet);registrationBean.setLoadOnStartup(1);registrationBean.addUrlMappings(/hystrix.stream);registrationBean.setName(HystrixMetricsStreamServlet);return registrationBean;}
} 启动 cloud-eureka-server7001 和 cloud-provider-hystrix-payment8001 模块使用 9001 监控 8001如下图 正常调用的测试地址为http://localhost:8001/payment/circuit/31异常调用的测试地址为http://localhost:8001/payment/circuit/-31先访问正确地址再访问异常地址再正确地址会发现图示断路器都是慢慢放开的。 实心圆共有两种含义。它通过颜色的变化代表了实例的健康程度它的健康度从绿色 黄色 橙色 红色递减。该实心圆除了颜色的变化之外它的大小也会根据实例的请求流量发生变化流量越大该实心圆就越大。所以通过该实心圆的展示就可以在大量的实例中快速的发现故障实例和高压力实例。 曲线用来记录 2 分钟内流量的相对变化可以通过它来观察到流量的上升和下降趋势。 整图说明如下