隔离策略

线程和线程池

客户端(库, 网络调用等)在各自的线程上运行. 这种做法将他们与调用线程隔开, 因此调用者可以从一个耗时的依赖调用”离开(walk away)”

Hystrix使用单独的, 每个依赖的线程池作为约束任何给定依赖的一种方式, 因此潜在执行的延迟将仅在该池中使可用线程饱和.

如果不试用线程池可以保护你免受故障的影响, 但是这需要客户端可信任地快速失败(网络连接/读取超时, 重试的配置)并始终表现良好.

在Hystrix的设计中, Netflix选择试用线程和线程池来达到隔离的目的, 原因有:

  • 很多应用程序调用了由很多不同的团队开发的许多(有时超过1000)不同的后端服务
  • 每个服务都各自提供了其客户端库
  • 客户端库不断地在更新
  • 客户端库可能被添加使用新的网络调用
  • 客户端库的逻辑中可能包含重试, 数据解析, 缓存(内存或者跨网络)和其他类似的行为
  • 客户端库更类似于一个黑盒, 其实现细节, 网络访问模式, 默认配置等是对使用者不透明的
  • 在实际的生产问题中, 根源经常是 “有些东西改变了, 配置应该被修改” 或者 “客户端库修改了逻辑”
  • 即使客户端没有改变, 服务端自身发生了变会员. 这种变化会是客户端设置无效而影响性能特性
  • 传递依赖会引入其他客户端, 这些客户端不是可预期的, 也可能没有被正确地配置
  • 大多数网络访问是同步的
  • 失败和延迟也可能发生在客户端, 不只是网络调用

线程池的优势

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

简而言之, 由线程池提供的隔离功能可以使客户端库和子系统性能特性的不断变化和动态组合得到适度处理, 而不会造成中断.

注意: 尽管单独的线程提供了隔离, 但你的底层客户端代码也应该有超时 和/或 响应线程中断, 以便它不会无限制地阻塞并使Hystrix线程池饱和.

线程池的缺点

线程池的主要缺点是增加了计算开销, 每个Command的执行设计到队列, 调度和Command单独运行的线程的上下文的切换.

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

线程成本

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

Netflix API每天使用线程隔离处理10亿多Hystrix Command执行. 每个API实例都有40多个线程池, 每个线程池中有5-20个线程(大多数设置为10).

信号量

你可以使用信号量(或计数器)来限制对任何给定依赖项的并发调用数量, 而不是使用线程池/队列大小. 这允许Hystrix在不使用线程池的情况下卸载负载. 如果你信任下客户端, 而你只想要卸载, 你可以使用这种方法.

HystrixCommandHystrixObservableCommand支持2个地方的信号量:

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

你可以通过动态属性来配置这两种信号量的使用, 这些动态属性定义了可以执行多少个并发线程. 在调整线程池大小时, 你应该使用类似的计算来调整它们的大小(内存调用返回的次毫秒时间可以在信号量仅为1或2的情况下执行超过5000rps, 但默认值为10).

一旦达到限制, 信号量拒绝将开始, 但填充信号量的线程不能离开.

翻译自How it Works