Spring Cloud 进阶--Netflix/Hystrix 基本理论及其工作原理概述

版权声明:本文为博主原创文章,如果觉得写的不错需要转载,在转载时请注明博文出处! https://blog.csdn.net/Hello_World_QWP/article/details/88568378

                            《 Netflix/Hystrix 基本理论及其工作原理概述 》

前言

本篇文章主要对 Netflix/Hystrix 进行了基本的介绍,包括什么是 Hystrix ?Hystrix 的作用?Hystrix 的工作流程?Hystrix 熔断器原理?Hystrix 隔离性原理?Hystrix 中的线程和线程池?使用线程池的优点?使用线程池的缺点?Hystrix 在使用线程池时的成本管控?Hystrix 中的 Semaphores ?Hystrix 的折叠请求?折叠请求的优点/缺点?Hystrix 为什么要使用折叠请求?使用折叠请求付出的代价?Hystrix 中的请求缓存等。

Hystrix 基本理论概述

1、关于 Hystrix

Hystrix 是一个用于处理分布式系统的 延迟容错 的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障(题外话:去年的6月份以及今年的3月份的阿里云的大规模云生态系统故障、宕机最后导致客户的数据损失,都是因为某个微小的误操作引起的大规模级联故障),以提高分布式系统的弹性。

断路器 ” 本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。


2、Hystrix 的作用

服务熔断、服务降级、服务限流、服务实时监控(Hystrix-Dashboard)

1)、服务熔断

熔断模式:这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。

2)、服务降级

Hystrix 服务降级:其实就是线程池中单个线程障处理,防止单个线程请求时间太长,导致资源长期被占有而得不到释放,从而导致线程池被快速占用完,导致服务崩溃。


Hystrix 能解决如下降级问题:

  • 请求超时降级,线程资源不足降级,降级之后可以返回自定义数据
  • 线程池隔离降级,分布式服务可以针对不同的服务使用不同的线程池,从而互不影响
  • 自动触发降级与恢复
  • 实现请求缓存和请求合并

3)、服务限流

限流模式:主要是提前对各个类型的请求设置最高的 QPS 阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求有可能造成雪崩效应。

4)、服务实时监控

监控流量的相对变化,可以通过对流量的起伏趋势来判断当前系统服务的被调用情况以及服务的健康状况,并且作出适当的服务调整,以给用户带来良好的体验效果或者作为系统内部的预警提醒。


3、Hystrix 工作流程图

下图反应了当您通过 Hystrix 向服务依赖项发出请求时的变化情况:

下面详细解释了 Hystrix 向服务依赖项发出请求时的过程变化(与上图的编号对应):

  1. 构造一个HystrixCommand或HystrixObservableCommand对象
  2. 执行命令
  3. 是否已经缓存了响应的内容?
  4. 电路打开了吗?
  5. Thread Pool/Queue/Semaphore 是否已满?
  6. HystrixObservableCommand.construct() 或者 HystrixCommand.run()
  7. 计算电路健康状况
  8. 获得回退
  9. 返回成功的响应信息

4、熔断器

下图显示了HystrixCommand或HystrixObservableCommand如何与hystrixcircuit断路器交互,以及逻辑和决策流程,包括计数器在断路器中的行为:

断路器的开闭原则如下:

  1. 假设电路中的体积满足一定的阈值- HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()
  2. 假设误差百分比超过阈值误差百分比- HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
  3. 然后断路器将从闭合过渡到打开
  4. 当它打开时,它会短路所有对断路器的请求
  5. 在一段时间之后-  HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds(),下一个请求将被允许通过(这是半打开状态)。如果请求失败,断路器将在休眠窗口期间返回到打开状态。如果请求成功,则断路器转换为 CLOSED,逻辑转换为1,再到步骤1)开始,就这样重复。

5、Hystrix 隔离性

Hystrix 使用 bulkhead 模式将依赖项彼此隔离,并限制对其中任何一个的并发访问:


6、线程和线程池

客户端(库、网络调用等)在单独的线程上执行。这将它们与调用线程(Tomcat 线程池)隔离开来,以便调用者可以从占用太长时间的依赖项调用中 “ 退出 ”。

Hystrix 使用单独的、每个依赖关系的线程池作为约束任何给定依赖关系的一种方式,因此底层执行的延迟将使该线程池中的可用线程饱和,如下图所示:

不使用线程池就可以防止失败,但是这需要客户端达到非常快地失败( 网络连接/读取超时和重试配置) ,并且始终表现良好。

在设计 Hystrix 时,Netflix 选择使用线程和线程池来实现隔离,原因有很多(以下列出了其10大理由),包括:

  1. 许多应用程序针对许多不同的团队开发的许多不同的服务执行几十个(有时甚至超过100个)不同的后端服务调用。
  2. 每个服务都提供自己的客户端库。
  3. 客户端库逻辑可以更改为添加新的网络调用。
  4. 客户端库可以包含逻辑,例如重试、数据解析、缓存(内存中或跨网络)以及其他类似的行为。
  5. 客户端库往往是 “ 黑盒 ” ——对用户来说,实现细节、网络访问模式、配置默认值等都是不透明的。
  6. 在一些实际的生产中断中,判断是 “ 哦,有些东西改变了,属性应该调整 ” 或者 “ 客户机库改变了它的行为 ”。
  7. 即使客户端本身没有改变,服务本身也可以改变,这可能会影响性能特征,从而导致客户端配置无效。
  8. 可传递的依赖关系可以引入其他客户端库,这些客户端库不是预期的,也可能没有正确配置。
  9. 大多数网络访问是同步执行的。
  10. 失败和延迟也可能发生在客户端代码中,而不仅仅是在网络调用中。

如下图的用户请求过程:

7、使用线程池的优点

通过线程池中的线程进行隔离的好处是:

  1. 应用程序完全受到保护,不会受到失控客户端库的影响。 给定依赖项库的池可以填满,而不会影响应用程序的其余部分。
  2. 应用程序可以以极低的风险接受新的客户端库。 如果一个问题发生,它是孤立的库,并不会影响其它所有东西。
  3. 当失败的客户端恢复正常时,线程池将被清除,应用程序立即恢复正常的性能,而不是在整个 Tomcat 容器不堪重负时进行长时间恢复。
  4. 如果客户端库配置不当,线程池的健康状况将迅速证明这一点( 通过增加错误、延迟、超时、拒绝等),您可以处理它(通常是通过动态属性实时处理的),而不影响应用程序的功能。
  5. 如果客户端服务改变了性能特征( 这种情况经常发生,足以成为一个问题) ,从而导致需要调优属性( 增加/减少超时、更改、重试等),那么通过线程池指标(错误、延迟、超时、拒绝) ,这又会变得可见,并且可以在不影响其它客户端请求或用户的情况下进行处理。
  6. 除了拥有隔离的好处之外,拥有专用线程池还提供了内置的并发性,可以利用这种并发性在同步客户端库之上构建异步外观(类似于 Netflix API 在 Hystrix 命令之上构建了一个反应性的、完全异步的 Java API)。

简而言之,线程池提供的隔离性允许在不引起中断的情况下,优雅地处理客户端库和子系统性能特征的不断变化和动态组合。

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

 

8、使用线程池的缺点

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

在设计这个系统的过程中,Netflix 决定接受这笔开销的成本,以换取它提供的好处,并认为这笔开销很小,不会对成本或性能产生重大影响。

 

9、Hystrix 在使用线程时的成本开销

Hystrix 测量在子线程上执行 construct()  或 run()  方法时的延迟以及在父线程上的总体端到端时间。通过这种方式,您可以看到 Hystrix 开销的成本(线程、指标、日志、断路器等)。

使用线程隔离,Netflix API 每天处理100 多亿次的 Hystrix 命令执行。 每个 API 实例都有 40 多个线程池,每个线程池中有 5-20 个线程(大多数被默认设置为10个)。

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

在中间位置(或更低位置),拥有一个单独的线程是没有成本的。在到 90% 的线位时,拥有一个单独的线程的成本为 3ms。在到 99% 的线位时,拥有一个单独的线程的成本为 9ms。 但是请注意,成本的增加远远小于单独线程(网络请求)执行时间的增加,该线程的执行时间从 2 跳到 28,而成本从 0 跳到 9。对于大多数 Netflix 用例来说,这种高于 90% 的开销被认为是可以接受的,因为这样可以体现出弹性的用处所在。


什么时候不建议使用线程池:

对于非常低延迟请求(比如那些主要触发内存缓存的请求)的电路,开销可能太高,在这种情况下,您可以使用另一种方法,比如可尝试使用 Semaphores,虽然它们不允许超时,但在没有开销的情况下提供了大部分的弹性好处。 然而,总的来说,开销很小,以至于 Netflix 实际上通常更喜欢使用单独线程的隔离优势,而不是 Semaphores 这样的技术。


10、关于 Semaphores (前面在有关线程池的文章中介绍过,这儿再简单介绍哈)

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

HystrixCommand 和 HystrixObservableCommand 在一下两个地方支持 Semaphores:

Fallback(回退):当 Hystrix 检索回退时,它总是在调用 Tomcat 线程上执行。

Execution(执行):如果将 Execution.isolation.strategy 属性设置为 SEMAPHORE,那么 Hystrix 将使用 Semaphores 而不是线程来限制调用该命令的并发父线程的数量。

当然您可以通过定义可以执行多少并发线程的动态属性来配置 Semaphores 的这两种用法。但您应该使用与调整 ThreadPool 大小时类似的计算方法来调整它们的大小(以毫秒为单位返回的内存调用可以执行超过 5000rps,Semaphores 仅为1或2,但默认值是10)。

*注意:如果依赖关系使用 Semaphores 来隔离,然后变成潜在的,那么父线程将保持阻塞状态,直到底层网络调用超时。一旦达到限制,信号量拒绝将启动,但是填充 Semaphores 的线程不能离开。


11、折叠请求

关于折叠请求:

请求折叠使外部服务上的许多并发请求能够被批处理为一个请求。例如,如果用户想为 300 个视频对象加载书签,而不是对 WEB 服务执行 300 个网络调用,这些请求可以批量处理为一个请求。请求可以围绕批处理大小和自批处理启动以来的运行时间展开。

折叠请求的优点:

  • 减少执行请求所需的线程和网络连接的数量
  • 减少外部服务的负载

折叠请求的缺点:

  • 在执行实际命令之前的延迟增加。 最大成本是批处理窗口的大小

例如:

下面的示例取自: Hystrix-Request Collapsing。 稍微修改一下,批量窗口的大小增加到2秒。该示例演示了正在执行的四个并发调用,并展示了如何将四个并发请求批处理在一起。为了保持示例的简洁性,下面的时序图只展示了前两个调用过程:

为什么要使用折叠请求?

使用折叠请求来减少执行并发 HystrixCommand 所需的线程和网络连接数。 请求折叠以自动方式完成此操作,而不会强制代码库的所有开发人员协调手动批处理请求。

全局上下文(跨所有Tomcat线程)?

理想的折叠类型是在全局应用程序级别完成的,这样依赖不管来自任何的 Tomcat 线程上的任何用户的请求都可以折叠在一起。例如,如果你配置 HystrixCommand 来支持批处理任何用户对依赖项的请求,这个依赖项可以检索评级,那么当同一 JVM 中的任何用户线程发出这样的请求时,Hystrix 将把它的请求和其他任何请求一起添加到同一个折叠的网络调用中。

*注意:折叠器将向折叠的网络调用传递一个 HystrixRequestContext 对象,因此下游系统必须处理这种情况,才能使其成为有效的选项。

请求折叠的代价是什么?

启用请求折叠的成本是在执行实际命令之前的延迟。 最大成本是批处理窗口的大小。如果您的命令平均执行时间为 5ms,而批处理窗口为 10ms,那么在最坏的情况下,执行时间可能变为 15ms。在通常情况下,请求不会恰好在窗口打开时提交到窗口,因此其中损失是窗口时间的一半,在这种情况下为 5ms。这个开销是否值得,取决于执行的命令。高延迟的命令不会因为少量额外的平均延迟而受到影响。

此外,给定命令上的并发量也是关键:如果一起处理的请求很少超过一个或者两个,那么折叠请求就毫无意义。 实际上,在单线程顺序迭代中,由于每次迭代都要等待 10ms 的批处理窗口时间,因此折叠将成为主要的性能瓶颈。

然而,如果一个特定的命令被大量并发地使用,并且可以批量处理几十甚至几百个调用,那么由于 Hystrix 减少了它所需的线程数量和依赖关系的网络连接数量,因此增加的吞吐量通常远远超过了成本。
 

折叠流程:


12、请求缓存

Hystrixcommand 和 HystrixObservableCommand 可以定义一个缓存键,然后用于以并发感知的方式在请求上下文中去反复调用。

下面是一个请求缓存示例流程图,其中涉及一个 HTTP 请求的生命周期和两个线程在该请求中执行的动作过程:
 

请求缓存的好处是:不同的代码路径可以执行 Hystrix 命令,而不必担心重复工作

这在大型代码库中特别有用,因为许多开发人员实现了不同的功能片段。例如,代码中的多个路径都需要获得一个用户的 Account 对象,每个请求可以是这样的:

Account account = new UserGetAccount(accountId).execute();

//或者

Observable<Account> accountObservable = new UserGetAccount(accountId).observe();

Hystrix RequestCache 将只执行底层 run() 方法一次,执行 HystrixCommand 的两个线程将收到相同的数据,尽管已经实例化了不同的实例( 数据检索在整个请求中是一致的)。每次执行命令时可能返回不同的值(或回退),第一个响应将被缓存,并为同一请求中的所有后续调用返回(消除重复的线程执行)。

因为请求缓存位于 construct() 或 run() 方法调用之前,所以 Hystrix 可以在调用导致线程执行之前解除它们。

如果 Hystrix 没有实现请求缓存功能,那么每个命令都需要在 construct() 或 run() 方法中实现自己的请求缓存功能,这将把它放在线程排队和执行之后。

3、Hystrix 源码地址
《 Netflix/Hystrix 》


 好了,关于 Netflix/Hystrix 基本理论及其工作原理概述 就写到这儿了,如果还有什么疑问或遇到什么问题欢迎扫码提问,也可以给我留言哦,我会一一详细的解答的。 
歇后语:“ 共同学习,共同进步 ”,也希望大家多多关注CSND的IT社区。


作       者: 华    仔
联系作者: [email protected]
来        源: CSDN (Chinese Software Developer Network)
原        文: https://blog.csdn.net/Hello_World_QWP/article/details/88568378
版权声明: 本文为博主原创文章,请在转载时务必注明博文出处!

猜你喜欢

转载自blog.csdn.net/Hello_World_QWP/article/details/88568378