系统中某个接口响应全部超时,dubbo线程池被全部占满,并堆积了大量待处理任务,整个应用无法响应任何外部请求,处于“假死”的状态,过去几分钟后,应用居然自己自动恢复了。
这是怎么回事呢?这里有两个问题让我们疑惑不解:
日志中明显的指出,这个http请求Read timed out。http请求中读超时设置过长的话,最终的效果会和慢查一样,导致线程长时间占用线程池资源(dubbo线程池),简言之,老的出不去,新的进不来。带着疑问,我们翻到了代码。
但是代码中确实是设置了读超时的,那么延后的错误日志是怎么来的呢?我们已经接近真相了吗?
我们不免对这个RestTemplateBuilder起了疑心,是这个家伙有什么暗藏的设置嘛?针对这个工具类,将线上的情况回放到本地进行了模拟。我们构建了500个线程同时使用这个工具类去请求一个http接口,这个http接口让每个请求都等待2秒后再返回,具体的做法很简单就是Thread.sleep(2000),然后观察每次请求的response和rt。
我们发现response都是正常返回的(没有触发Read timed out),rt是规律的5个一组并且有2秒的递增。看到这里,大家是不是感觉到了什么?对!这里面有队列!通过继续跟踪代码,我们找到了“元凶”。
这个工具类默认使用了队列去发起http请求,形成了类似pool的方式,并且pool active size仅有5。
现在我们来还原下整个案件的经过:
到这里,大家心里的一块石头已经落地。但回顾整个案件,无非就是我们工作中或者面试中,经常碰到或被问到的一个问题:“对象池是怎么用的呢?线程池是怎么用的呢?队列又是怎么用的呢?它们的核心参数是怎么设置的呢?”。答案是没有标准答案,核心参数的设置,一定需要根据场景来。拿本案举例,本案涉及两个方面:
发起http请求的队列
这个使用队列形成的pool的场景是侧重IO的操作,IO操作的一个特性是需要有较长的等待时间,那我们就可以为了提高吞吐量,而适当的调大pool active size(反正大家就一起等等咯),这和线程池的的maximum pool size有着异曲同工之处。那调大至多少合适呢?可以根据这个接口调用情况,平均QPS是多少,峰值QPS是多少,rt是多少等等元素,来调出一个合适的值,这一定是一个过程,而不是一次性决定的。那又有同学会问了,我是一个新接口,我不知道历史数据怎么办呢?对于这种情况,如果条件允许的话,使用压测是一个不错的办法。根据改变压测条件,来调试出一个相对靠谱的值,上线后对其观察,再决定是否需要调整。
dubbo线程池
在本案中,对于这个线程池的问题有两个,队列长度与拒绝策略。队列长度的问题显而易见,一个应用的负载能力,是可以通过各种手段衡量出来的。
就像我们去餐厅吃饭一样,顾客从上桌到下桌的平均时间(rt)是已知的,餐厅一天存储的食物也是已知的(机器资源)。回到本案,如果我们调低了队列的长度,增加了适当的拒绝策略,并且可以把长时间排队的任务移除掉(这么做有一定风险),可以一定程度的提高系统恢复的速度。
最后补一句,我们在使用一些第三方工具包的时候(就算它是spring的),需要了解其大致的实现,避免因参数设置不全,带来意外的“收获”。
线程池的提交过程
AbortPolicy:任务过量,不处理,直接抛异常
CallerRunsPolicy:任务过量,哪来的回哪去,谁把任务给线程池的,就谁去执行,这里是main线程扔给线程池的,因此main线程执行,不抛异常
DiscardPolicy:过量的任务直接放弃,不抛异常
DiscardOldestPolicy:过量的任务会尝试和最早的竞争,不抛异常
案例分析
Executor executor = new ThreadPoolExecutor(1, 1, 60 * 3, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
executor.execute(线程1);
executor.execute(线程2);
executor.execute(线程3);
r1.stop();
r2.stop();
r3.stop();
线程处理逻辑
while (!stop) {
Thread.sleep(1000L);
if (stop) {
break;
}
System.out.plintln("当前线程:"+Thread.currentThread())
}
结果:线程1和线程3死循环执行。
原因:线程1是核心线程,处于循环执行中,线程2放到队列中等待线程1完成,线程3看到队列满,又达到最大线程数,采取拒绝策略,根据CallerRunsPolicy策略要交给主线程执行。由于主线程处于循环逻辑,于是下面stop逻辑执行不到,进而线程1和线程3死循环执行。
页面更新:2024-04-20
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号