Hystrix工作原理(二)

8. Get the Fallback

每当命令执行失败时,Hystrix都会尝试恢复您的回退:当construct()或run()(6.)抛出异常时,当命令因电路打开而被短路时(4.),当命令的线程池和队列或信号量处于容量(5.),或者当命令超过其超时长度时。

编写您的回退以从内存缓存或通过其他静态逻辑提供没有任何网络依赖性的通用响应。如果必须在后备中使用网络调用,则应通过其他HystrixCommand或HystrixObservableCommand进行。

对于HystrixCommand,要提供回退逻辑,请实现HystrixCommand.getFallback(),它返回单个回退值。

在HystrixObservableCommand的情况下,要提供回退逻辑,您需要实现HystrixObservableCommand.resumeWithFallback(),它返回可能会发出一个或多个回退值的Observable。

如果回退方法返回响应,则Hystrix将此响应返回给调用者。在HystrixCommand.getFallback()的情况下,它将返回一个Observable,它发出从该方法返回的值。在HystrixObservableCommand.resumeWithFallback()的情况下,它将返回从该方法返回的相同Observable。

如果您尚未为Hystrix命令实现回退方法,或者回退本身会引发异常,则Hystrix仍会返回一个Observable,但不会发出任何异常,并立即以onError通知终止。通过此onError通知,导致命令失败的异常将传回给调用者。(实现可能失败的回退实现是一种不好的做法。您应该实现回退,使其不执行任何可能失败的逻辑。)

失败或不存在的回退的结果将根据您调用Hystrix命令的方式而有所不同:

  • execute() - 抛出异常
  • queue() - 成功返回Future,但如果调用其get()方法,则此Future将抛出异常
  • observe() - 返回一个Observable,当你订阅它时,会立即通过调用订阅者的onError方法终止
  • toObservable() - 返回一个Observable,当您订阅它时,将通过调用订阅者的onError方法终止

9. Return the Successful Response 

如果Hystrix命令成功,它将以Observable的形式将响应或响应返回给调用者。根据您在上面的步骤2中调用命令的方式,此Observable可能会在返回给您之前进行转换:

  • execute() — 以与.queue()相同的方式获取Future,然后在此Future上调用get()以获取Observable发出的单个值
  • queue() — 将Observable转换为BlockingObservable,以便将其转换为Future,然后返回此Future
  • observe() — 立即订阅Observable并开始执行命令的流程;返回一个Observable,当您订阅它时,重放排放和通知
  • toObservable() —返回Observable不变;您必须订阅它才能真正开始导致执行命令的流程

Sequence Diagram

@ adrianb11友好地提供了一个演示上述流程的序列图

Circuit Breaker

下图显示了HystrixCommand或HystrixObservableCommand如何与HystrixCircuitBreaker及其逻辑和决策流程进行交互,包括计数器在断路器中的行为方式。

熔断器开关条件:

  1. 如果请求量到达了指定值(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold)
  2. 如果异常比率超过了指定值(HystrixCommandProperties.circuitBreakerErrorThresholdPercentage)
  3. 熔断器将状态从CLOSE设置为OPEN.
  4. 熔断器状态打开后,所有请求都会被直接熔断。
  5. 在经过指定窗口期(HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds)后,状态将会被设置为HALF-OPEN,如果该请求失败了,状态重新被设置为OPEN并且等待下一个窗口期,如果请求成功了,状态设置为CLOSE。

Isolation

Hystrix使用隔板模式来隔离彼此的依赖关系,并限制对其中任何一个的并发访问。

Threads & Thread Pools

客户端(库,网络调用等)在不同的线程上执行。这将它们与调用线程(Tomcat线程池)隔离开来,这样调用者就可以从一个耗时太长的依赖调用中“离开”。

Hystrix使用单独的每依赖性线程池作为约束任何给定依赖性的方式,因此底层执行的延迟将仅使该池中的可用线程饱和。

您可以在不使用线程池的情况下防止故障,但这需要客户端被信任非常快速地失败(网络连接/读取超时和重试配置)并始终表现良好。

Netflix在其Hystrix设计中选择使用线程和线程池来实现隔离,原因有很多,其中包括: 

  • 许多应用程序针对由许多不同团队开发的数十种不同服务执行数十种(有时甚至超过100种)不同的后端服务调用。
  • 每个服务都提供自己的客户端库。
  • 客户端库一直在变化。
  • 客户端库逻辑可以更改为添加新的网络调用。
  • 客户端库可以包含诸如重试,数据解析,缓存(内存中或跨网络)以及其他此类行为的逻辑。
  • 客户端库往往是“黑盒子” - 用户对实现细节,网络访问模式,配置默认值等不透明。在几个真实的生产中断中,决心是“哦,某些东西改变了,属性应该调整”或“客户端库改变了它的行为”。
  • 即使客户端本身没有发生变化,服务本身也会发生变化,从而影响性能特征,从而导致客户端配置无效。
  • 传递依赖性可以引入其他未预期且可能未正确配置的客户端库。
  • 大多数网络访问是同步执行的。客户端代码中也可能出现故障和延迟,而不仅仅是在网络呼叫中。

Benefits of Thread Pools

通过线程在自己的线程池中隔离的好处是:

  • 该应用程序完全受到失控客户端库的保护。给定依赖库的池可以填满,而不会影响应用程序的其余部分。
  • 该应用程序可以接受风险较低的新客户端库。如果出现问题,它将与库隔离,不会影响其他所有内容。
  • 当失败的客户端再次变得健康时,线程池将清空并且应用程序立即恢复正常性能,而不是在整个Tomcat容器不堪重负时进行长时间恢复。
  • 如果客户端库配置错误,线程池的运行状况将很快证明这一点(通过增加的错误,延迟,超时,拒绝等),您可以处理它(通常通过动态属性实时),而不会影响应用程序功能。
  • 如果客户端服务改变了性能特征(这通常发生在一个问题上),这反过来导致需要调整属性(增加/减少超时,更改重试等),这又通过线程池指标(错误,延迟)变得可见,超时,拒绝),可以在不影响其他客户,请求或用户的情况下进行处理。
  • 除了隔离优势之外,拥有专用线程池还提供内置并发性,可用于在同步客户端库之上构建异步外观(类似于Netflix API在Hystrix命令之上构建反应式,完全异步Java API的方式)。

简而言之,线程池提供的隔离允许优雅地处理客户端库和子系统性能特征的始终改变和动态组合,而不会导致中断。

注意:尽管隔离是一个单独的线程提供的,但您的底层客户端代码也应该有超时和/或响应线程中断,这样它就无法无限期地阻塞并使Hystrix线程池饱和。

Drawbacks of Thread Pools

线程池的主要缺点是它们增加了计算开销。每个命令执行都涉及在单独的线程上运行命令所涉及的排队,调度和上下文切换

Netflix在设计这个系统时决定接受这笔费用的成本,以换取它提供的好处,并认为它足够小,不会产生重大的成本或性能影响。

Cost of Threads

Hystrix在子线程上执行construct()或run()方法时测量延迟,以及在父线程上执行总端到端时间。

通过这种方式,您可以看到Hystrix开销(线程,指标,日志记录,断路器等)的成本。Netflix API每天使用线程隔离处理10亿个Hystrix Command执行。每个API实例都有40多个线程池,每个线程池中有5-20个线程(大多数设置为10)。

下图表示在单个API实例上以每秒60个请求执行的一个HystrixCommand(每个服务器每秒大约350个线程执行):

在中位数(和更低),有一个单独的线程没有成本。

在第90个百分位处,有一个单独的线程需要3毫秒的成本。

在第99百分位数,有一个单独的线程花费9毫秒。但请注意,成本的增加远远小于单独线程(网络请求)的执行时间的增加,该线程从2跳到28,而成本从0跳到9。

对于大多数Netflix使用案例而言,这些电路在第90百分位数和更高电平下的开销被认为是可接受的,以实现弹性的好处。

对于包含非常低延迟请求的电路(例如那些主要触及内存缓存的电路),开销可能过高,在这种情况下,您可以使用其他方法,如可尝试的信号量,虽然它们不允许超时,无需开销即可提供大部分弹性优势。然而,一般来说,开销很小,以至于Netflix在实践中通常更喜欢单独线程相对于这些技术的隔离优势。

Semaphores

您可以使用信号量(或计数器)来限制对任何给定依赖项的并发调用数,而不是使用线程池/队列大小。这允许Hystrix在不使用线程池的情况下卸载负载,但它不允许超时和离开。如果您信任客户端而您只想减载,则可以使用此方法。

HystrixCommand和HystrixObservableCommand支持2个地方的信号量:

  • Fallback:当Hystrix检索回退时,它总是在调用Tomcat线程上执行此操作。
  • Execution:如果将属性execution.isolation.strategy设置为SEMAPHORE,则Hystrix将使用信号量而不是线程来限制调用该命令的并发父线程数。

您可以通过定义可以执行多少并发线程的动态属性来配置信号量的这两种用法。

您应该使用在调整线程池大小时使用的类似计算来调整它们的大小(以毫秒为单位返回的内存中调用可以在5000rps下执行,信号量仅为1或2 ......但默认值为10)。

注意:如果依赖项与信号量隔离然后变为潜在的,则父线程将保持阻塞状态,直到基础网络调用超时。信号量拒绝将在限制被触发后开始,但填充信号量的线程无法走开。

猜你喜欢

转载自blog.csdn.net/u013702678/article/details/88774516