hystrix官网地址github
Hystrix同样是netfix公司在分布式系统中的贡献。同样的也进入的不维护阶段。不维护不代表被淘汰。只能说明推陈出新技术在不断迭代。曾今的辉煌曾经的设计还是值得我们去学习的。
在分布式环境中,服务调度是特色也是头疼的一块。在服务治理章节我们介绍了服务治理的功能。前一课我们也介绍了ribbon、feign进行服务调用。现在自然的到了服务监控管理了。hystrix就是对服务进行隔离保护。以实现服务不会出现连带故障。导致整个系统不可用
Hystrix是一个低延迟和容错的第三方组件库。旨在隔离远程系统、服务和第三方库的访问点。官网上已经停止维护并推荐使用resilience4j。但是国内的话我们有springcloud alibaba。
Hystrix 通过隔离服务之间的访问来实现分布式系统中延迟及容错机制来解决服务雪崩场景并且基于hystrix可以提供备选方案(fallback)。
99.9 9 30 = 99.7 % u p t i m e 0.3 % o f 1 b i l l i o n r e q u e s t s = 3 , 000 , 000 f a i l u r e s 2 + h o u r s d o w n t i m e / m o n t h e v e n i f a l l d e p e n d e n c i e s h a v e e x c e l l e n t u p t i m e . 99.99^{30} = 99.7\% \quad uptime \\ 0.3\% \quad of \quad 1 \quad billion \quad requests \quad = \quad 3,000,000 \quad failures \\ 2+ \quad hours \quad downtime/month \quad even \quad if \quad all \quad dependencies \quad have \quad excellent \quad uptime. 99.9930=99.7%uptime0.3%of1billionrequests=3,000,000failures2+hoursdowntime/monthevenifalldependencieshaveexcellentuptime.
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
演示下传统企业没有备选方案的情况会发生什么灾难。
上面的场景发生是因为payment#createByOrder 和payment#getTimeOut/id同属于payment服务。一个payment服务实际上就是一个Tomcat服务。同一个tomcat服务是有一个线程池的。 每次请求落到该tomcat 服务里就会去线程池中申请线程。获取到线程了才能由线程来处理请求的业务。就是因为tomcat内共享线程池。所以当payment#getTimeOut/id并发上来后就会抢空线程池。导致别的借口甚至是毫不相关的接口都没有资源可以申请。只能干巴巴的等待资源的释放。
这就好比上班高峰期乘坐电梯因为某一个公司集中上班导致一段时间电梯全部被使用了。这时候国家领导过来也没办法上电梯。
我们也知道这种情况很好解决。每个园区都会有专用电梯供特殊使用。
我们解决上述问题也是同样的思路。进行隔离。不同的接口有不同的线程池。这样就不会造成雪崩。
@HystrixCommand( groupKey = "order-service-getPaymentInfo", commandKey = "getPaymentInfo", threadPoolKey = "orderServicePaymentInfo", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000") }, threadPoolProperties = { @HystrixProperty(name = "coreSize" ,value = "6"), @HystrixProperty(name = "maxQueueSize",value = "100"), @HystrixProperty(name = "keepAliveTimeMinutes",value = "2"), @HystrixProperty(name = "queueSizeRejectionThreshold",value = "100") }, fallbackMethod = "getPaymentInfoFallback" ) @RequestMapping(value = "/getpayment/{id}",method = RequestMethod.GET) public ResultInfo getPaymentInfo(@PathVariable("id") Long id) { log.info(Thread.currentThread().getName()); return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, ResultInfo.class); } public ResultInfo getPaymentInfoFallback(@PathVariable("id") Long id) { log.info("已经进入备选方案了,下面交由自由线程执行"+Thread.currentThread().getName()); return new ResultInfo(); } @HystrixCommand( groupKey = "order-service-getpaymentTimeout", commandKey = "getpaymentTimeout", threadPoolKey = "orderServicegetpaymentTimeout", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "10000") }, threadPoolProperties = { @HystrixProperty(name = "coreSize" ,value = "3"), @HystrixProperty(name = "maxQueueSize",value = "100"), @HystrixProperty(name = "keepAliveTimeMinutes",value = "2"), @HystrixProperty(name = "queueSizeRejectionThreshold",value = "100") } ) @RequestMapping(value = "/getpaymentTimeout/{id}",method = RequestMethod.GET) public ResultInfo getpaymentTimeout(@PathVariable("id") Long id) { log.info(Thread.currentThread().getName()); return orderPaymentService.getTimeOut(id); }
并发量在getpaymentTimeout | getpaymentTimeout/{id} | /getpayment/{id} |
---|---|---|
20 | 三个线程打满后一段时间开始报错 | 可以正常响应;也会慢,cpu线程切换需要时间 |
30 | 同上 | 同上 |
50 | 同上 | 也会超时,因为order调用payment服务压力会受影响 |
@HystrixCommand( commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000"), @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value = "SEMAPHORE"), @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value = "6") }, fallbackMethod = "getPaymentInfoFallback" )
措施 | 优点 | 缺点 | 超时 | 熔断 | 异步 |
---|---|---|---|---|---|
线程隔离 | 一个调用一个线程池;互相不干扰;保证高可用 | cpu线程切换开销 | √ | √ | √ |
信号量隔离 | 避免CPU切换。高效 | 在高并发场景下需要存储信号量变大 | × | √ | × |
在上面我们的timeout接口。不管是线程隔离还是信号量隔离在条件满足的时候就会直接拒绝后续请求。这样太粗暴了。上面我们也提到了fallback。
还记的上面我们order50个并发的timeout的时候会导致getpayment接口异常,当时定位了是因为原生payment服务压力撑不住导致的。如果我们在payment上加入fallback就能保证在资源不足的时候也能快速响应。这样至少能保证order#getpayment方法的可用性。
但是这种配置属于实验性配置。在真实生产中我们不可能在每个方法上配置fallback的。这样愚蠢至极。
hystrix除了在方法上特殊定制的fallback以外,还有一个全局的fallback。只需要在类上通过@DefaultProperties(defaultFallback = "globalFallback")
来实现全局的备选方案。一个方法满足触发降级的条件时如果该请求对应的HystrixCommand
注解中没有配置fallback则使用所在类的全局fallback。如果全局也没有则抛出异常。
DefaultProperties
可以避免每个接口都配置fallback。但是这种的全局好像还不是全局的fallback。我们还是需要每个类上配置fallback。笔者查阅了资料好像也没有FallbackFactory
这个类吗。这个类可以理解成spring的BeanFactory
。这个类是用来产生我们所需要的FallBack
的。我们在这个工厂里可以生成一个通用类型的fallback的代理对象。代理对象可以根据代理方法的方法签名进行入参和出参。FallBackFactory
感兴趣的可以下载源码查看或者进主页查看openfeign专题。@HystrixCommand( 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"), //失败率达到多少后跳闸 }, fallbackMethod = "getInfoFallback" ) @RequestMapping(value = "/get", method = RequestMethod.GET) public ResultInfo get(@RequestParam Long id) { if (id < 0) { int i = 1 / 0; } log.info(Thread.currentThread().getName()); return orderPaymentService.get(id); } public ResultInfo getInfoFallback(@RequestParam Long id) { return new ResultInfo(); }
circuitBreaker.requestVolumeThreshold
设置统计请求次数circuitBreaker.sleepWindowInMilliseconds
设置时间滑动单位 , 在触发熔断后多久进行尝试开放,及俗称的半开状态circuitBreaker.errorThresholdPercentage
设置触发熔断开关的临界条件http://localhost/order/get?id=-1
进行20次测试。虽然这20次无一例额外都会报错。但是我们会发现一开始报错是因为我们代码里的错误。后面的错误就是hystrix熔断的错误了。一开始试by zero 错误、后面就是short-circuited and fallback failed 熔断错误了order/getId?id=1
突然有一万个请求过来。为了缓解压力我们集中一下请求每100个请求调用一次order/getIds?ids=xxxxx
。这样我们最终到payment模块则是10000/100=100个请求。下面我们通过代码配置实现下请求合并。@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface HystrixCollapser { String collapserKey() default ""; String batchMethod(); Scope scope() default Scope.REQUEST; HystrixProperty[] collapserProperties() default {}; }
属性 | 含义 |
---|---|
collapserKey | 唯一标识 |
batchMethod | 请求合并处理方法。即合并后需要调用的方法 |
scope | 作用域;两种方式[REQUEST, GLOBAL] ; |
REQUEST : 在同一个用户请求中达到条件将会合并
GLOBAL : 任何线程的请求都会加入到这个全局统计中 |
| HystrixProperty[] | 配置相关参数 |
@HystrixCollapser( scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL, batchMethod = "getIds", collapserProperties = { @HystrixProperty(name = HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH , value = "3"), @HystrixProperty(name = HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS, value = "10") } ) @RequestMapping(value = "/getId", method = RequestMethod.GET) public ResultInfo getId(@RequestParam Long id) { if (id < 0) { int i = 1 / 0; } log.info(Thread.currentThread().getName()); return null; } @HystrixCommand public List<ResultInfo> getIds(List<Long> ids) { System.out.println(ids.size()+"@@@@@@@@@"); return orderPaymentService.getIds(ids); }
我们通过jemeter进行getId接口压测,日志中ids的长度最大是3 。 验证了我们上面getId接口的配置。这样就能保证在出现高并发的时候会进行接口合并降低TPS。
上面我们是通过请求方法注解进行接口合并处理。实际上内部hystrix是通过HystrixCommand
官网给出的流程图示,并配备流程说明一共是9部。下面我们就翻译下。
①、创建HystrixCommand或者HystrixObservableCommand对象
HystrixCommand : 用在依赖单个服务上
HystrixObservableCommand : 用在依赖多个服务上
②、命令执行,hystrrixCommand 执行execute、queue ; hystrixObservableCommand执行observe、toObservable
方法 | 作用 |
---|---|
execute | 同步执行;返回结果对象或者异常抛出 |
queue | 异步执行;返回Future对象 |
observe | 返回Observable对象 |
toObservable | 返回Observable对象 |
hystrix 除了服务熔断、降级、限流以外,还有一个重要的特性是实时监控。并形成报表统计接口请求信息。
关于hystrix的安装也很简单,只需要在项目中配置actutor和hystrix-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>
EnableHystrixDashboard
就引入了dashboard了。 我们不需要进行任何开发。这个和eureka一样主需要简单的引包就可以了。就这样dashboard搭建完成了。dashboard主要是用来监控hystrix的请求处理的。所以我们还需要在hystrix请求出将端点暴露出来。
在使用了hystrix命令的模块加入如下配置即可,我就在order模块加入
@Component public class HystrixConfig { @Bean public ServletRegistrationBean getServlet(){ HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1); //注意这里配置的/hystrix.stream 最终访问地址就是 localhost:port/hystrix.stream ; 如果在配置文件中配置在新版本中是需要 //加上actuator 即 localhost:port/actuator registrationBean.addUrlMappings("/hystrix.stream"); registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; } }
localhost/hystrix.stream
就会出现ping的界面。表示我们order模块安装监控成功。当然order也需要actuator模块hystrix-dashboard
来对我们的order模块进行监控。但是实际应用中我们不可能只在order中配置hystrix的。<!--新增hystrix dashboard--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-turbine</artifactId> </dependency>
spring: application: name: cloud-hystrix-turbine eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka instance: prefer-ip-address: true # 聚合监控 turbine: app-config: cloud-order-service,cloud-payment-service cluster-name-expression: "'default'" # 该处配置和url一样。如果/actuator/hystrix.stream 的则需要配置actuator instanceUrlSuffix: hystrix.stream
启动类上添加EnableTurbine
注解
源码: https://gitee.com/zxhTom/cloud-framework-root
作者:烟花散尽13141