在分布式架构中,一个应用依赖多个服务是非常常见的,如果其中一个依赖由于延迟过高发生阻塞,调用该依赖服务的线程就会阻塞,如果相关业务的QPS较高,就可能产生大量阻塞,从而导致该应用/服务由于服务器资源被耗尽而拖垮。
另外,故障也会在应用之间传递,如果故障服务的上游依赖较多,可能会引起服务的雪崩效应。就跟数据瘫痪,会引起依赖该数据库的应用瘫痪是一个道理。
hystrix 是 netflix 开发的在 SOA/微服务架构
中提供服务隔离、熔断、降级机,制的工具/框架,用于保障微服务架构中的高可用。
设计目标:
设计原则:
hystrix 工作流程流程:
HystrixCommand
或者 HystrixObservableCommand
对象execute
: 阻塞方法, 从依赖请求中接收单个响应或出错抛出异常queue
: 从依赖请求中返回一个包含单个响应的 Future
对象observe
: 订阅一个从依赖请求中返回的代表响应的 Observable
对象toObservalbe
: 返回一个 Observable
对象,只有当你订阅它时,它才会执行Hystrix命令并发射响应execute()
实际上就是调用queue().get()
,queue()
调用的是toObservable().toBlocking().toFuture()
.也就是说,最终每一个 HystrixCommand 都是通过 Observable
来实现的,即使这些命令仅仅是返回一个简单的值Observable
对象的形式返回getFallback
getFallback
HystrixCommand.run()
或 HystrixObservableCommand.construct()
,如果这两个方法执行超时或者执行失败,则执行getFallback()
HystrixCommand.run()
返回单个响应或者抛出异常HystrixObservableCommand.construct()
返回一个发射响应的Observable
或者发送一个 onError
的通知TimeoutException
异并执行 fallback
,并且如果run()或者construct()方法没有被取消或者中断,会丢弃这两个方法最终返回的结果。(注意:没有任何方式可以强制终止一个潜在线程[latent thread]的运行,Hystrix能够做的最好的方式是让JVM抛出一个InterruptedException异常.如果你的任务被Hystrix所包装,并不意味着会抛出一个InterruptedExceptions
异常,该线程在Hystrix的线程池内会进行执行,虽然在客户端已经接收到了TimeoutException
异常.)HystrixCircuitBreaker
工作流程:
allowRequest()
判断是否允许将请求提交到线程池circuitBreaker.forceOpen
为 true), 不允许放行, 返回circuitBreaker.forceClosed
为 true),允许放行。这种情况下不必关注熔断器实际状态,也就是说熔断器仍然会维护统计数据和开关状态,只是不生效isOpen()
判断熔断器开关是否打开circuitBreaker.requestVolumeThreshold
, 允许请求放行, 否则继续判断circuitBreaker.errorThresholdPercentage
, 允许放行。否则打开熔断器开关,进入第三步allowSingleTest()
判断是否允许单个请求通行, 检查依赖服务是否恢复
1)如果熔断器打开, 且距离熔断器打开的时间或上一次试探请求放行的时间超过circuitBreaker.sleepWindowInMilliseconds
的值时,熔断器器进入半开状态,允许放行一个试探请求;否则,不允许放行
2)为了提供决策依据,每个熔断器默认维护了10个bucket,每秒一个bucket,当新的bucket被创建时,最旧的bucket会被抛弃。其中每个blucket维护了请求成功、失败、超时、拒绝的计数器,Hystrix负责收集并统计这些计数器
3) 服务的健康状况 = 请求失败数 / 请求总数
, 熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的。熔断器的开关能保证服务调用者在调用异常服务时,快速返回结果,避免大量的同步等待。并且熔断器能在一段时间后继续侦测请求执行结果, 提供恢复服务调用的可能熔断器开关转换过程图:
Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离, 从而避免服务雪崩。
客户端(第三方包,网络调用等)会在单独的线程里面执行(把逻辑包装成 Command), 会与调用该任务的线程进行隔离, 以此防止调用者依赖的服务消耗时间
通过单独线程池隔离隔离调用的优点:
线程池的缺点: