Hystrix线程池隔离设计原则及接口限流实验

2022-11-30 15:12:53 浏览数 (1)

Hystrix 通过判断线程池或者信号量是否已满,超出容量的请求,直接 Reject 走降级,从而达到限流。

限流是限制对后端的服务的访问量,比如说你对 MySQL、Redis、Zookeeper 以及其它各种后端中间件的资源的访问的限制,其实是为避免过大流量直接打死后端的服务,限制服务对后端的资源的访问。

1 线程池隔离设计原则

Hystrix采取了bulkhead舱壁隔离技术,将外部依赖进行资源隔离,避免任何外部依赖的故障导致本服务崩溃。

舱壁隔离 是说将船体内部空间区隔划分成若干个隔舱,一旦某几个隔舱发生破损进水,水流不会在其间相互流动,如此一来船舶在受损时,依然能具有足够的浮力和稳定性,进而减低立即沉船的危险。

外部依赖的调用在单独的线程中执行,这样就能跟调用线程隔离开来,避免外部依赖调用timeout耗时过长,导致调用线程被卡死。

Hystrix对每个外部依赖用一个单独线程池,此时若对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池,不会影响其他的依赖调用。

2 线程池隔离适用场景

  • 每个服务都会调用几十个后端依赖服务
  • 每个后端依赖服务都会提供它自己的client调用库,比如用thrift,就会提供对应的thrift依赖
  • client调用库随时会变更
  • client调用库随时可能会增加新的网络请求的逻辑
  • client调用库可能会包含诸如自动重试,数据解析,内存中缓存等逻辑
  • client调用库一般都对调用者是个黑盒,包括实现细节,网络访问,默认配置等
  • 在生产环境,经常会出现调用者,突然发现,client调用库发生了某些变化
  • 即使client调用库没有改变,依赖服务本身可能有会发生逻辑上的变化
  • 有些依赖的client调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确
  • 大多数网络请求都是同步调用
  • 调用失败和延迟,也有可能会发生在client调用库本身的代码中,不一定就是发生在网络请求中

简单来说,即默认client调用库很不靠谱!而且随时可能发生各种变化,所以就要用强制隔离的方式来确保任何服务的故障不能影响当前服务。

3 优点

  • 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源已满,也不影响其他的服务调用
  • 服务可随时引入一个新的依赖服务 因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用
  • 当一个故障的依赖服务恢复正常,可通过清理掉线程池,瞬间恢复该服务的调用,(如果是tomcat线程池被占满,重恢复就很麻烦)
  • 若某client调用库配置出现问题,线程池的健康状况随时会报告,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机
  • 基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层

最大的好处就是资源隔离,确保任一依赖服务故障,不会拖垮当前服务。

4 缺点

  • 最大的缺点:增加CPU开销 除了Tomcat本身的调用线程之外,还有hystrix自己管理的线程池
  • 每个command执行都依托一个独立线程 会进行排队,调度,还有上下文切换
  • Hystrix官方做了一个多线程异步带来的额外开销,通过对比多线程异步调用 同步调用得出,Netflix API每天通过hystrix执行10亿次调用,每个服务实例有40个以上的线程池,每个线程池有10个左右的线程 用hystrix的额外开销,就是给请求带来了3ms左右的延时,最多延时在10ms以内,相比于可用性和稳定性的提升,这是可以接受的

我们可以用hystrix semaphore实现对某个依赖服务的并发访问量的限制,而不是通过线程池/队列的大小来限制流量。

sempahore可限流和削峰,但不能用来对调用延迟的服务进行timeout和隔离。

代码语言:javascript复制
execution.isolation.strategy,设置为SEMAPHORE

hystrix就会用semaphore替代线程池机制,来对依赖服务的访问进行限流。 如果通过semaphore调用的时候,底层的网络调用延迟很严重,则无法timeout,只能一直block住。一旦请求数量超过了semephore限定的数量之后,就会立即开启限流。

5 实战接口限流

一个线程池,大小是15个,队列大小是10个,timeout时长设置的长一些,5s。

模拟发送请求,然后写死代码,在command内部做一个sleep,比如每次sleep 1s,10个请求发送过去以后,直接被hang死,线程池占满。

再发送请求,就会堵塞在缓冲队列,queue,10个,20个,10个,后10个应该就直接reject,fallback逻辑。

15 10 = 25个请求,15在执行,10个缓冲在队列里了,剩下的流量全部被reject,限流,降级。

  • withCoreSize:设置你的线程池的大小
  • withMaxQueueSize:设置的是你的等待队列,缓冲队列的大小
  • withQueueSizeRejectionThreshold:如果withMaxQueueSize<withQueueSizeRejectionThreshold,那么取的是withMaxQueueSize,反之,取得是withQueueSizeRejectionThreshold

线程池本身的大小,如果你不设置另外两个queue相关的参数,等待队列是关闭的

queue大小,等待队列的大小,timeout时长

先进去线程池的是10个请求,然后有8个请求进入等待队列,线程池里有空闲,等待队列中的请求如果还没有timeout,那么就进去线程池去执行

10 8 = 18个请求之外,7个请求,直接会被reject掉,限流,fallback

withExecutionTimeoutInMilliseconds(20000):timeout也设置大一些,否则如果请求放等待队列中时间太长了,直接就会timeout,等不到去线程池里执行了 withFallbackIsolationSemaphoreMaxConcurrentRequests(30):fallback,sempahore限流,30个,避免太多的请求同时调用fallback被拒绝访问

0 人点赞