标签:上进 替代 关于 border 转换 连接数 thread png 健康检测
注:本文并非是精确的文档翻译,而是根据自己理解的整理,有些内容可能由于理解偏差翻译有误,有些内容由于是显而易见的,并没有翻译,而是略去了。本文更多是学习过程的产出,请尽量参考原官方文档。
下图描述了当通过Hystrix请求依赖服务时的流程:
通过将调用服务所需的参数传入commad的构造函数,创建commad对象。HystrixObserverbleCommand表示,调用的服务将返回一个Observable对象并且发射(emit)响应。
HystrixCommand command = new HystrixCommand(arg1, arg2); HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);
上述两种command有四种执行方式(前两种只适用于HystrixCommand ):
K value = command.execute(); Future<K> fValue = command.queue(); Observable<K> ohValue = command.observe(); //hot observable Observable<K> ocValue = command.toObservable(); //cold observable
同步调用execute()会调用queue().get()方法,queue()会调用toObservable().toBlocking().toFuture。所以,最终每个HystrixCommand都是Observable实现。
如对应依赖的响应缓存是打开的,并且缓存中存在对应响应,则会以Observable的形式从缓存中直接返回。
在执行commad时,将会检查断路器是否打开。
当断路器处于打开的状态时,将不会执行Commad,跳转到第8步执行降级策略。
如果断路器关闭,将会继续执行第5步,并且检查是否有足够的容量来执行命令。
当上述资源池已满,将不会执行Command,跳转到第8步执行降级策略。
通过执行上述方法中的一种,Hystrix实现对依赖的调用,其中:
如果run或者construct方法超时,对应线程将会抛出超时异常。此时进入降级流程,并且如果正常的服务调用没有取消而且最终得到响应,则此响应将被丢弃。
这里需要注意的是,由于没有办法确保一个线程强制结束,Hystrix最多能够向服务调用线程抛出一个InterruptedException。如果实际的工作线程没有对该异常进行正常响应,而是忽略;则Hystrix线程池将继续执行,此时客户端实际上已经得到了TimeoutException。尽管此时客户端的负载已被有效隔离,这种情形仍可能导致Hystrix线程池耗尽。大多数的Java Http Client组件库在得到InterruptedException时不会正常终止线程执行,因此,请确保服务调用的读写超时配置正确。
断路器通过维护一组计数器来统计回路的健康指标,Hystrix会将服务调用的成功、失败、拒绝、超时等次数发给断路器。
断路器通过统计数据来决定是否触发断路逻辑。断路器打开后,将经过一定的预设时间,在此期间所有的请求均不会实际执行。预设时间达到后,如果此时服务调用健康检测通过,则会关闭断路器。
当command执行失败(4、5、6步中提及的情形),Hystrix将触发降级。
使用Hystrix时,需要根据具体的场景写出降级逻辑,可以是从缓存中获取响应或者进入静态的处理逻辑。如果,此时仍旧必须通过网络调用来执行降级逻辑,则需要在另外一个Command中执行。
当使用HystrixCommand时,需要实现HystrixCommand.getFallBack()方法来实现降级策略。
当使用HystrixObservableCommand时,需要实现HystrixObservableCommand.resumeWithFallback(),其将会返回一个Observable对象。
当降级方法返回时,Hystrix向调用者相应返回。使用HystrixCommand.getFallback()时,返回Observable对象发射从降级方法中得到的响应。当使用HystrixObservableCommand.resumeWithFallback()时,返回降级方法实际返回的Observable对象。
如果在使用时未实现降级方法,或者降级方法本身会抛出异常,Hystrix仍然会返回Observable对象,但不会发射任何对象,而是会在onError通知后立即结束。通过onError通知,导致Command终止的异常被传递给调用方。(降级方法本身不应该失败,在实际中应该尽量避免这种情况)
触发Hystrix command的方式不同,失败的降级方法的结果不同:
当Hystrix command成功返回时,其通过Observable向调用者返回。实际返回的形式取决于第2步中触发command的方式:
下面的时序图,表示了HystrixCommand和断路器间的交互逻辑以及各统计判断过程。
断路器的开关状态切换如下:
Hystrix采用壁仓模式(bulkhead pattern)隔离依赖。
Hystrix 使用每个服务独立的线程池,这样一来,每个服务的延迟最坏只会造成该服务对应的线程池耗尽。
也可以不使用针对每个服务独立的线程池,在这种情况下,要保证服务不受外部依赖影响,则需要客户端能够快速失败并且能够正确返回失败信息。
Hystrix采用线程池作为服务隔离的手段主要有以下原因:
使用服务级独立的线程池,主要有以下好处:
总之,线程池隔离能够使得应用对于客户端库以及远程服务的改变迅速响应,优雅应对,避免资源耗尽。
采用线程池带来的主要负面影响是额外的计算开销。每个单独的command执行都包含排队、调度、上下文切换等等由于线程执行带来的开销。
Hystrix对父线程端到端执行的总时间消耗以及执行command的子线程的时间消耗都做了计量,以次来观测Hystrix带来的整体开销。
下图描述了一个每秒通过HystrixCommand处理每秒约60次请求(向后每秒调用约350次后台服务)的请求开销:
在中位数处,采用额外的线程没有带来明显的开销
在90%处,额外的线程带来的开销约为3ms
在99%处,额外的线程带来的开销约为9ms。相比于中位数处,实际服务调用时间从2增加到28,而额外的线程开销从0增加到9。
这种额外的开销相比起带来的益处在大多数情况下我们认为是可以接受的。
作为线程池的替代,Hystrix同样支持使用信号量(或计数器)限制某个服务的并发调用量。与线程池不同的是,信号量不支持超时计数(timing out)和walking away(这个咋翻。。。)。在信任客户端并且仅仅想达到负载限制的目的的情况下,可以采用这种方法。
HystrixCommand和HystrixObservableCommand在如下两个方面支持信号量:
降级:当Hystrix获取降级返回时,总是在调用容器线程上进行
执行:如果将execution.isolaiton.strategy设置为SEMOPHORE,则将采用信号量作为限制并发数的方法。
上述两个方面均可以通过动态参数的方式实现配置,来实现对并发线程数的限制。限制数的大小和线程池大小的估计方法类似。
注意:如果一个外部依赖时通过信号量的方式隔离的,并且发生了延迟,则父线程将同样保持阻塞,直至调用超时或者返回。
信号量并发数限制将会在达到后立即生效,但是已经阻塞的线程将继续保持阻塞,不能提前返回。
可以在HystrixCommand前置一个collapser,来实现对多个请求对应的后台服务调用的合并。
下图是采用和不采用请求合并的服务调用的示意图。
在并发请求下,使用请求合并主要用来降低并发的线程数及网络连接数。Hystrix采用自动的方式来完成请求合并,不需要开发者显示执行请求合并操作。
理想的合并应该是应用全局级别的,这样容器内所有用户的请求均合一被合并。
例如,如果对请求电影排行的一个服务做了请求合并配置,那么同一个JVM内的所有请求,都将别Hystrix合并到一个网络调用中。
需要注意的是,collapser会通过网络调用向下游系统发送一个HystrixRequestContext,下游系统必须能够正确处理这个context以便正确响应。
Hystrix还支持基于单个用户态的线程对请求做合并,比如某用户线程请求300部电影的标签信息,Hystrix会将300各请求合并成一个。
有些时候当在客户端设计了一个符合业务逻辑的对象模型后,通常会发现,这个模型在有效利用服务端资源方面可能并不好。
例如,对于一个获取300各电影的各自标签的场景,逐个迭代调用标签获取服务是最简单的逻辑,但是这种设计会在短时间内发起300个后台的服务调用请求,容易造成资源的耗尽。
对于这种场景,有常见的解决方式,。例如,在用户视角做限制,在获取电影标签前,必须让用户限定获取标签的电影范围,而不是全部电影。或者,可以对对象模型做分解,可以首先调用一个服务获取电影信息列表,再针对这个列表请求属性信息。
显而易见,这些设计可能导致用户不友好的API设计或者不符合现实逻辑的模型设计。同时,在许多开发者协同开发的应用系统中,这些特殊的设计很有可能被其他开发者修改而失效。
Hystrix通过将请求合并下沉到实际应用逻辑以外,使得对象模型的特殊设计与否、服务调用的顺序特殊考虑与否都不会对应用造成显著影响,甚至开发者也不需要做太多额外的业务逻辑外的优化。
很明显,请求合并带来的额外消耗主要是执行合并带来的时间延迟。其最大值便是合并的时间窗口的最大值。
例如,有一个外部调用花费5ms,并且通过Hystrix对其做了时间窗口为10ms的合并,那么,最坏情况下,这个外部调用将可能花费15ms。但是,一般来讲,并不是所有请求都在窗口刚刚打开的那一刻进来,因此,实际上做请求合并带来的平均额外时间消耗是5ms。
是否做请求合并的关键是外部请求本身的特性。对于一个本身耗时很高的调用,对其做请求合并带来的额外时间影响可能微乎其微。同时,对外部服务的并发调用数也是关键。显然没有必要对一个并发是1或者2的外部调用做请求合并。
然而,对于并发很高的外部调用,例如在合并窗口内并发可以达到几十或者上百,则做请求合并带来的额外延时相比于过多的资源消耗调用是可以忽略的。
请求缓存
HystrixCommand和HystrixObservableCommand的实现可以针对并发场景下的请求定义缓存key,来减少后续的实际网络请求。
下图展示了一个HTTP请求的生命周期及两个执行该请求的线程:
请求缓存的主要优势在于:
这在较大的团队中带来的好处通常更加明显。例如,对于获取用户账户的服务调用,多处代码都可能这样写:
Account account = new UserGetAccount(accountId).execute(); //or Observable<Account> accountObservable = new UserGetAccount(accountId).observe();
Hystrix的RequestCache将仅执行一次run()方法,所有执行HystrixCommand的线程都将得到相同的返回结果,尽管他们完全不同的线程实例。
当第一个返回被缓存后,在同一个请求中,后续的command执行的返回都将是一致的。
Request cache位于执行run()或者executed()方法之前,因此可以消除实际执行调用带来的重复线程执行消耗。
【Reference】
https://github.com/Netflix/Hystrix/wiki/How-it-Works
标签:上进 替代 关于 border 转换 连接数 thread png 健康检测
原文地址:https://www.cnblogs.com/luojiahu/p/9938739.html