Hystrix
服务故障引起的雪崩:
多个微服务之间调用时,A调用B和C,B和C又调用其他服务,这就是所谓扇出,如果链路上某个微服务由于网络或自身原因导致服务响应时间过长或不可用,对A的调用就会占用越来越多的系统资源,进而引起系统崩溃。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。所以,通常当发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩
Hystrix:
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,通过Hystrix可以通过 添加等待时间容限或容错逻辑来控制分布式服务之间的调用。Hystrix通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,提高系统整体稳定性
雪崩解决方案:
请求缓存:对于访问量高,且数据变动不频繁的数据,使用缓存降低DB和服务压力
请求合并:将相同的请求进行合并然后调用批处理接口
服务隔离:限制调用分布式服务的资源,某一个调用的服务出现问题不会影响其他服务调用
服务熔断:牺牲局部服务,保全整体系统稳定性的措施
服务降级:服务熔断以后,客户端调用自己本地方法返回缺省值
线程池隔离:
- 概念:采用舱壁隔离技术,将外部依赖进行资源隔离,避免任何外部依赖故障导致本服务崩溃。对每个外部依赖用一个单独线程池,如果某个外部依赖调用延迟很严重,最多是耗尽那个依赖自己的线程池,不影响其他依赖调用。在用户请求和服务之间加入线程池,用户请求不能直接访问服务,通过线程池中空闲线程来访问服务,如果线程池满了,会进行降级,用户请求不会阻塞或超时,至少可以看到一个执行结果,防止无休止等待和系统崩溃。
- 优点:可以安全隔离依赖的服务,减少所依赖服务发生故障时的影响面、当失败的服务再次变得可用时,线程池将清理并立即恢复,不需要一个长时间的恢复、独立的线程池提高了并发性
- 缺点:请求在线程池中执行,会带来任务调度,排队和上下文切换带来的CPU开销、涉及到跨线程,就存在ThreadLocal数据的传递问题,比如在主线程初始化的ThreadLocal变量,在线程中无法获取
- 特性:请求线程和调用服务线程不是同一条线程(请求线程是tomcat线程,调用服务线程是hystrix线程池的线程)、支持超时可直接返回,支持熔断,当线程池到达最大线程数后,再请求会触发fallback接口进行熔断、隔离原理:每个服务单独用线程池,支持同步和异步两种方式、资源消耗大,大量线程的上下文切换、排队、调度等,无法传递Http Header
信号量隔离:
概念:每次调用线程,当前请求通过计数信号量进行限制,当信号量大于最大请求数时,进行限制,调用fallback接口快速返回。信号量是同步的,每次调用都要阻塞调用方的线程,直到结果返回,导致了无法对访问做超时,只能依靠调用协议超时,无法主动释放
特性:请求线程和调用服务线程是同一条线程(tomcat线程)、不支持超时、支持熔断,当信号量达到maxConcurrentRequests后,再请求会触发fallback接口进行熔断
使用总结:
- 请求并发大,耗时长(计算大,或操作关系型数据库),采用线程隔离策略。这样可以保证大量的线程可用,不会由于服务原因一直处于阻塞或等待状态,快速失败返回。还有就是对依赖服务的网络请求的调用和访问,会涉及timeout这种问题的都使用线程池隔离。
- 请求并发大,耗时短(计算小,或操作缓存) ,采用信号量隔离策略,因为这类服务的返回通常会非常的快,不会占用线程太长时间,而且也减少了线程切换的开销,提高了缓存服务的效率。还有就是适台访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,像这种访问系统内部的代码,不涉及任何的网络请求,做信号量的普通限流就可以了,因为不需要去捕获timeout类似的问题,并发量突然太高,稍微耗时一些导致很多线程卡在这里,所以进行一个基本的资源隔离和访问,避免内部复杂的低效率的代码,导致大量的线程被夯住。
服务熔断:由于某些原因使服务出现过载现象,为防止造成整个系统故障,从而采用的一种保护错误,也叫过载保护

服务降级:请求满足降级条件,直接走fallback,满足以下条件触发:
- 方法抛出HystrixBadRequestException异常
- 方法调用超时或熔断器开启拦截调用
- 线程池/队列/信号量跑满
- command 执行超时
- run() 或者 construct() 抛出异常
hystrix执行原理:

请求缓存:对于一个request context内的多个相同command,使用request cache,提升性能
断路器原理:
判断滑动窗口中,至少有多少个请求才可能触发断路。即在一定时间内经过断路器的流量必须达到设定的值,才会去判断要不要断路
判断异常比例,断路器统计到的异常调用占比超过一定阈值(默认50%),即在一定时间内经过断路器的流量超过了阈值,同时异常访问的数量也达到了设定的比例,就会开启断路
断路开启,由close转换到 open 状态。之后在 SleepWindowInMilliseconds 时间内,所有经过该断路器的请求全部都会被断路,不调用后端服务,直接走 fallback 降级机制。而在该参数时间过后,断路器会变为半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 close 状态。
属性设置:
- Enabled:控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值是 true。
- ForceOpen:如果设置为 true 的话,直接强迫打开断路器,相当于是手动断路了,手动降级,默认值是 false
- ForceClosed:如果设置为 true,直接强迫关闭断路器,相当于手动停止断路了,手动升级,默认值是 false。
- requestVolumeThreshold:设置滑动窗口中,最少要有多少个请求时,才触发开启短路
- sleepWindowInMilliseconds:设置短路后,需要多长时间内直接拒绝请求,这个时间后,重新变为半开闭。
- errorThresholdPercentage:异常请求百分比,默认50%。
Hystrix参数配置:
- execution.isolation.strategy:指定资源隔离策略,THREAD或SEMAPHORE。THREAD基于线程池,每个command运行在一个线程中,限流通过线程池的大小控制。SEMAPHORE基于信号量,command运行在调用线程中,通过信号量的容量来进行限流
- command:每个command都可以设置一个名称和一个组。通过command group来定义一个线程池,并聚合一些监控和报警信息,同一个command group的请求会进入同一个线程池。默认threadpool key就是command group的名称。
- coreSize:线程池大小,默认10
- queueSizeRejectionThreshold:队列的最大大小,默认值5。HystrixCommand提交到线程池之前,会先进入一个队列,队列满后,才会拒绝。
- execution.isolation.semaphore.maxConcurrentRequests:使用信号量策略时,允许访问的最大并发量,超过这个并发量直接被拒绝。默认是10。信号量是基于调用线程去执行command的,而且不能从timeout中抽离,因此一旦设置的太大,而且有延时发生,可能瞬间导致tomcat本身的线程资源本占满
Hystrix选择用线程池机制来进行资源隔离,要面对的场景如下:
- 每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的
- 每个后端依赖服务都会提供它自己的client调用库,比如说用thrift的话,就会提供对应的thrift依赖
- client调用库随时会变更
- client调用库随时可能会增加新的网络请求的逻辑
- client调用库可能会包含诸如自动重试,数据解析,内存中缓存等逻辑
- client调用库一般都对调用者来说是个黑盒,包括实现细节,网络访问,默认配置,等等
- 在真实的生产环境中,经常会出现调用者,突然间惊讶的发现,client调用库发生了某些变化
- 即使client调用库没有改变,依赖服务本身可能有会发生逻辑上的变化
- 有些依赖的client调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确
- 大多数网络请求都是同步调用的
- 调用失败和延迟,也有可能会发生在client调用库本身的代码中,不一定就是发生在网络请求中
线程池隔离技术的设计原则:舱壁隔离技术,来将外部依赖进行资源隔离,进而避免任何外部依赖的故障导致本服务崩溃。Hystrix对每个外部依赖用一个单独的线程池,这样的话,如果对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池而已,不会影响其他的依赖调用。必须默认远程调用库就很不靠谱,而且随时可能各种变化,所以就要用强制隔离的方式来确保任何服务的故障不能影响当前服务
线程池机制的优点:
- 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用
- 服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用
- 当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是tomcat线程池被占满,再恢复就很麻烦
- 如果一个client调用库配置有问题,线程池的健康状况随时会报告,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机
- 如果一个服务本身发生了修改,需要重新调整配置,此时线程池的健康状况也可以随时发现,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机
- 基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层
线程池机制的缺点:
- 最大的缺点就是增加了cpu的开销
- 每个command的执行都依托一个独立的线程,会进行排队,调度,还有上下文切换
- Hystrix官方自己做了一个多线程异步带来的额外开销,通过对比多线程异步调用+同步调用得出,Netflix API每天通过hystrix执行10亿次调用,每个服务实例有40个以上的线程池,每个线程池有10个左右的线程。最后发现说,用hystrix的额外开销,就是给请求带来了3ms左右的延时,最多延时在10ms以内,相比于可用性和稳定性的提升,这是可以接受的
本文来自博客园,作者:难得,转载请注明原文链接:https://www.cnblogs.com/zhangbLearn/p/18829374

浙公网安备 33010602011771号