线程隔离的线程池配置最佳姿势
先谈谈qps,rt和线程数的关系
单机单线程能够处理的最大qps为1000ms/rt(抛开cpu利用率等等其他的因素)
比如任务的rt为100ms,那么单机单线程理论上能够处理的最大值为10qps
对于多线程场景,最大qps =(1000/rt)* coreSize * cpu利用率。(假定统一设置coreSize=maxSize)
比如我的dubbo服务,默认最大线程池为200,那么最大qps =(1000/rt)* 200 * cpu利用率
注意:rt在不同并发度下的表现可能不一样。
可以参考看看这篇文章,讲得比较清楚: https://blog.csdn.net/sinat_34976604/article/details/88125707
再谈谈线程数和系统最大并发数的关系
假设服务端只有10个线程,同一时刻有16个请求并发打过来,这时系统会将10个请求立刻执行,另外6个请求则需要在queue中等待。
只有当有一个线程执行完,可以理解为线程空了一个槽位,才会执行在queue中排队的一个任务。所以线程数决定了系统中在同一时刻最大并行的任务数,也就是最大并发数。 如果我们想要让一个线程在一秒内处理多个请求,后面的请求势必要在队列中等待。
进入正题
1. 为什么要使用线程隔离?
为了避免由个别下游依赖出现问题,影响其他依赖的使用资源,占用大量线程资源,导致链路rt升高,进而造成雪崩效应
2. 怎么配置核心线程数?
coreSize = qps / (timout/rt)
qps:打到下游最大的qps, timeout:对下游能够忍受的最大rt, rt:下游rt
-
公式解析
这个公式直接拿出来可能不太好理解,没关系,我们先看前人总结的公式,我对这个公式进行了优化
原始版本:
coreSize=qps * rt + buffer 我们先抛开buffer这一项来看,这个等式等价于coreSize= qps / (1000/rt)
很容易想到,这个计算思路就是: qps除以单线程能够处理的最大qps,得出的就是所需要的线程数
-
网上已有公式,为什么还要提出这个变种的公式呢?
- 按1000ms打满算,但是我的业务并不允许一个20ms的请求,排队执行等到1000ms才返回
- 公式里面简单粗暴的+buffer,每个下游处理情况不一样要怎么加buffer呢?心里是不是没谱
-
优化分析
很容易发现,优化后的公式与原公式的差别是将1000变量化了,可以根据各个业务场景适配。timeout/rt还是一个线程需要分担的qps,但是这样可以量化到最排队在最后的任务也不会超过timeout这个时间。而不是简单粗暴的对coreSize加buffer
线程池coresize.png
3. 如何配置队列大小?
queueSize = 最大并发数 - coreSize
-
为什么是最大并发数而不是qps?
举个例子,我们dubbo服务最大线程数200,此时260个请求同时打过来,只有200个请求能够立即执行。
假设对下游的调用是1:1的,那么打到下游最大的并发数也是200,只有下游执行成功一个,下游隔离的线程池空了一个位置,并且这个请求做完剩下所有的事情成功返回,才会将队列中的等待的60个请求漏一个下来。
所以我们只要保证我的隔离线程池每时每刻都能够承接下最大的并发数即可。
服务并发处理情况.png
4. 对每个下游独立线程池开销很大,有没有办法优化?
-
满足 同一来源 && 不会同时执行 这两个特征的下游依赖,可以考虑合并线程池管理
比如系统对A和B两个下游接口依赖,如果A B都只在同一个中调用,并且这个接口对A和B的执行一定是串行的,那么可以将这两个接口的线程池合并。
线程池合并.png可以看到执行完A空闲的时候,线程池可以处理另一个request的B,这样可以提高线程池的利用率
需要注意的是代入公式计算的时候,可以把A和B看成一个整体,将AB的rt之和代入计算。
性能压测验证
-
压测场景
购物车加购,目标qps260,配置下游线程池coreSize:87,queue:120
爬坡1分钟,持续10分钟
-
压测表现
-
未开启hystrix,平均rt 70ms
未开启 加购压测数据.png
-
-
开启hystrix之后,平均rt 69ms
开启后加购压测数据.png
-
是不是纳闷加入了排队机制rt不升反降了?
我们来看看压测期间下游rt情况
-
未开启hystrix,下游rt 29.24ms
未开启下游rt情况.png -
开启hystrix后,下游rt 28.92ms
开启下游rt情况.png
-
-
结论
对下游的并发度,会影响下游的处理能力,如果配置的线程数是 下游并发处理能力的拐点时,这时候的效果应该会比较好。所以想要满足要求的线程池配置通过上面的公式很容易配置,但是对下游并发度的控制,也就是timeout的取值,就需要压测慢慢调,才能拿到最优的参数了。
引申思考
线程池隔离的思想也能够用来做限流, 对某个接口分配一个线程池,以线程池大小来控制这个接口的最大并发量。理论上对应限制的qps可以认为是(1000/rt) * coreSize