线程池ThreadPoolTaskExecutor使用不当的惨痛教训

编程入门 行业动态 更新时间:2024-10-11 00:30:58

线程池ThreadPoolTaskExecutor使用不当的<a href=https://www.elefans.com/category/jswz/34/1769581.html style=惨痛教训"/>

线程池ThreadPoolTaskExecutor使用不当的惨痛教训

问题现场:
  • 配置:生产环境nginx做负载均衡,后端三台服务器,这样一个传统的集群架构。
  • 现象:平时系统正常时,没怎么发现问题。最近随着业务量增大,我们以及依赖的一些第三方服务接连出现各种各样的服务超时,不可用的情况。最终我们也没有幸免,因为我们的业务依赖第三方接口的成分较大,属于一个用户接收,用户渠道的角色。这也就意味着如果第三方服务不可用,我们的相应服务也将不可用了。没错,最终我们的系统挂了!
  • 原因:原因是服务里面的一个接口不可用了,调用总是超时。而接口调用是在一个异步线程池中进行的。
	<bean id="taskExecutor" class="java.util.concurrent.ThreadPoolExecutor" p:corePoolSize="2"p:maxPoolSize="5" />

嗯,这是我们的一个线程池配置。是以前的一个老代码,因为缺少定期的代码评审 + 繁忙的业务逻辑任务,代码疏于管理,就这样了。

问题分析

熟悉线程池的同学可以知道,上面的线程池就是Executors创建的线程池了,可以参考:Executors创建线程池会造成OOM问题

1、它的阻塞队列是一个无界队列,队列里面最多可用容纳的任务可达Integer.MAX_VALUE个,当阻塞的任务过多,势必会产生oom异常。

2、线程池中线程数的配置。我前面说了,我们的业务基本都是依赖了调用第三方接口的这样一个场景,在这里,如果该线程池中的线程需要调用第三方接口,这是一个IO密集型的线程池(网络IO甚至比磁盘IO还要慢上几十倍),设置2个线程大小显然是不合理的。同时因为阻塞队列的长度为Integer.MAX_VALUE,即使任务很多,线程池大小也不会扩容到p:maxPoolSize=“5”个。在这种场景下,两个较慢的请求就会占用整个线程池,后面的请求都将排队了。

3、还有一个致命的问题(前面没说),就是多个业务逻辑公用了这个线程池。更加加剧的线程池的压力,且因为一个服务接口不可用,导致调用其他可用接口的服务也同时不可用,几乎全面崩盘。

4、没有使用一个有效的拒绝策略。当巨大的流量过来时,在线程池这一层也可以做一层拒绝策略。如提示用户 “系统过于火爆,请稍后再试!”。外层也进行请求限流,甚至服务熔断以保证服务能存活下来。这些我们都没有。

问题小结

首先服务不可用起初是出现在第三方系统,但最终蔓延到我们系统。同时又因为我们使用线程池的不合理(多个服务公用一个线程池,线程池线程数、阻塞队列、拒绝策略的等问题),导致了整个服务不可用。

解决方案

1、线程池应做到尽量隔离,独立。即每个需要异步执行的业务逻辑应独立配置一个线程池,这样不至于业务服务之间不影响。这样,一个接口不可用,也只是这个线程池处于阻塞状态,其他线程池的对应服务还能继续正常执行。

2、线程池参数做到合理配置。配置规则为:

线程数
计算密集型(比如都是做数据运算,不涉及IO操作的),线程数可以配置为和cpu核数差不多。(配置过多即使cpu处于空闲状态,过多的线程数只会增加线程切换,降低效率)
IO密集型:这个就不好说了,我觉得需要看任务中IO操作与计算操作的比例。假如都是IO操作,需要cpu,那么你可以设置的尽量大(但又不能过大,因为系统的线程数是有限制的)。如果计算与IO操作各一半,那么就类似于计算密集型的个数*2,我相信你也理解了。

阻塞队列
阻塞队列可以按照自身系统的用户使用量,业务的容许延迟程度,内存的宽裕程度等考虑。可以设置1000-几万不等

3、线程池要配置合理的拒绝策略。人生就是这样,有舍才有得。作为一名软件设计者,你不能让有限的硬件软件资源能够处理无限的请求吧。所以设置合理的拒绝策略是有必要的。java.util.concurrent.ThreadPoolExecutor中提供的四种拒绝策略有在线程直接执行,抛出异常,直接抛弃任务、抛弃阻塞队列中最老的任务。在真实场景中差强人意,你可以实现java.util.concurrent.RejectedExecutionHandler接口实现一个自定义拒绝策略,抛出一个更友好的提示等。

小结

做到以上几点以后,即使某个第三方接口服务不可用,在大部分情况下,能保证本系统的安全。当然因为第三方接口服务不可用,当前功能的不可用也是无奈之举,有数据不一致问题也只能后期运维。谁叫服务没有很好地做到高可用呢!

更多推荐

线程池ThreadPoolTaskExecutor使用不当的惨痛教训

本文发布于:2024-03-04 07:05:27,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1708573.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:惨痛   线程   不当   教训   ThreadPoolTaskExecutor

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!