可用架构(10)-Hystrix隔离策略、Command及资源池大小控制

网友投稿 799 2022-05-30

1 隔离策略 - ExecutionIsolationStrategy

执行HystrixCommand时使用

指定HystrixCommand.run()的资源隔离策略。

资源隔离,要解决的最核心的问题,就是将多个依赖服务的调用分别隔离到各自资源池内。避免对某个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有线程资源全部耗费在该服务的接口调用上。

1.1 THREAD - 线程池隔离

线程池隔离技术,并非指控制类似tomcat web容器的线程。严格意义说,hystrix的线程池隔离技

术,控制tomcat线程的执行。线程池满后,确保tomcat的线程不会因为依赖服务的接口调用延迟或故障,被hang住,fallback , ms。tomcat其他的线程不会卡死,快速返回,然后可以支撑其他的事情。

线程池和信号量做资源隔离、限流、容量的限制,默认的容量都是10。

线程池隔离技术是用自己的线程去执行调用的。

信号量隔离技术,是直接让tomcat的线程去调用依赖服务的。

默认的策略为线程池。

THREAD:在单独的线程上执行HystrixCommand#run方法,使用线程池大小限制并发

基于线程池

HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)

1

2

优点

支持排队和超时

支持异步调用

不足

线程调用会产生额外的性能开销。

容易由于创建大量线程而 OOM,所以 sentinel 只支持信号量隔离。

适用场景

适合绝大多数的场景,线程池,对依赖服务的网络请求的调用和访问,超时这种问题。

高可用架构(10)-Hystrix隔离策略、Command及资源池大小控制

不受信客户

有限扇出

1.2 SEMAPHORE - 信号量隔离

SEMAPHORE:在调用线程上执行HystrixCommand#run方法,使用信号量许可计数限制

基于信号量

HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)

1

2

线程池机制,每个command运行在一个线程中,限流是通过线程池的大小来控制的

信号量机制,command是运行在调用线程中,但是通过信号量的容量来进行限流

优点

轻量,无额外开销。

不足

不支持任务排队和主动超时

不支持异步调用

适用场景

适合访问不是对外部依赖的访问,而是对内部比较复杂业务逻辑的访问。

但像这种访问,系统内部代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流即可。

因为不需要去捕获类似超时的问题,算法效率要求不是太高。并发量突然太高,因为这里稍微耗时一些,导致很多线程卡在这里的话,不太好,所以进行一个基本的资源隔离和访问,避免内部复杂的低效率代码,导致大量线程夯死。

受信客户

高扇出(网关)

高频高速调用

使用信号量场景,通常是针对超大并发量,每个服务实例每秒都几百QPS。

此时如果用线程池,线程一般不会太多,可能撑不住高并发。要撑住,可能要耗费大量线程资源,那么就用信号量,来限流保护。

一般用信号量常见于那种基于纯内存服务,而不涉及到任何网络访问请求。

netflix有100+的command运行在40+的线程池中,只有少数command是不运行在线程池中的,就是从纯内存中获取一些元数据,或者是对多个command包装起来的facacde command,是用信号量限流的。

比如缓存服务,可能会将部分量特别少,访问又特别频繁的一些数据,放在纯内存。

一般我们在获取到商品数据之后,都要去获取商品是属于哪个地理位置,省,市,卖家的

可能在自己的纯内存中,比如就一个Map去获取。对于这种直接访问本地内存的逻辑,比较适合用信号量做一下简单的隔离。

优点在于,不用自己管理线程池,不用担心超时,信号量做隔离的话,性能会相对高。

坑点

2 command名称 & command组

2.1 command名称

每个command,都可设置自己的名称,同时可以设置一个自己的组。

private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")); public CommandHelloWorld(String name) { super(cachedSetter); this.name = name; }

1

2

3

4

5

6

7

8

2.2 command group

默认情况下,因为就是通过command group来定义一个线程池,而且还会通过command group聚合一些监控和报警信息。

同一command group中的请求,都会进入同一个线程池中。

3 command线程池

ThreadPoolKey代表了一个HystrixThreadPool,用来进行统一监控,统计,缓存

默认的threadpool key就是command group名称

每个command都会跟它的ThreadPoolKey对应的ThreadPool绑定

如果不想直接用command group,也可以手动设置thread pool name

public CommandHelloWorld(String name) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool"))); this.name = name; }

1

2

3

4

5

6

command threadpool => command group => command key

command key

代表了一类command,代表底层的依赖服务的一个接口

command group

代表了某一个底层的依赖服务,合理,一个依赖服务可能会暴露出来多个接口,每个接口就是一个command key

command group

在逻辑上去组织起来一堆command key的调用,统计信息,成功次数,timeout超时次数,失败次数,可以看到某一个服务整体的一些访问情况

推荐是根据一个服务去划分出一个线程池,command key默认都是属于同一个线程池的

比如说你以一个服务为粒度,估算出来这个服务每秒的所有接口加起来的整体QPS在100左右

你调用那个服务的当前服务,部署了10个服务实例,每个服务实例上,其实用这个command group对应这个服务,给一个线程池,量大概在10个左右,就可以了,你对整个服务的整体的访问QPS大概在每秒100左右

一般来说,command group是用来在逻辑上组合一堆command的

举个例子,对于一个服务中的某个功能模块来说,希望将这个功能模块内的所有command放在一个group中,那么在监控和报警的时候可以放一起看

command group,对应了一个服务,但是这个服务暴露出来的几个接口,访问量很不一样,差异非常之大

你可能就希望在这个服务command group内部,包含的对应多个接口的command key,做一些细粒度的资源隔离

对同一个服务的不同接口,都使用不同的线程池

command key -> command group command key -> 自己的threadpool key

1

2

3

逻辑上来说,多个command key属于一个command group,在做统计的时候,会放在一起统计

每个command key有自己的线程池,每个接口有自己的线程池,去做资源隔离和限流

但对于thread pool资源隔离来说,可能是希望能够拆分的更加一致一些,比如在一个功能模块内,对不同的请求可以使用不同的thread pool

command group一般来说,可以是对应一个服务,多个command key对应这个服务的多个接口,多个接口的调用共享同一个线程池

如果说你的command key,要用自己的线程池,可以定义自己的threadpool key,就ok了

4 coreSize

设置线程池的大小,默认是10

HystrixThreadPoolProperties.Setter() .withCoreSize(int value)

1

2

一般来说,用这个默认的10个线程大小就够了

5 queueSizeRejectionThreshold

控制queue满后reject的threshold,因为maxQueueSize不允许热修改,因此提供这个参数可以热修改,控制队列的最大值

HystrixCommand在提交到线程池之前,其实会先进入一个队列中,这个队列满了之后,才会reject

默认值是5

HystrixThreadPoolProperties.Setter() .withQueueSizeRejectionThreshold(int value)

1

2

线程池+queue的工作原理

6 isolation.semaphore.maxConcurrentRequests

设置使用SEMAPHORE隔离策略的时候,允许访问的最大并发量,超过这个最大并发量,请求直接被reject

这个并发量的设置,跟线程池大小的设置,应该是类似的

但是基于信号量的话,性能会好很多,而且hystrix框架本身的开销会小很多

默认值是10,设置的小一些,否则因为信号量是基于调用线程去执行command的,而且不能从timeout中抽离,因此一旦设置的太大,而且有延时发生,可能瞬间导致tomcat本身的线程资源本占满

Tomcat 任务调度

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:DAOS 分布式异步对象存储|Container
下一篇:HDC.Cloud2021 | 知识计算助力医学科研探索,辅助提升新冠药物筛选效率
相关文章