一次压测问题总结:java线程池不消费

一、 项目背景
目前比较热门的发展发现是音视频,当前项目底层使用的软交换平台是FreeSwitch,java通过esl和FreeSwitch交互,其底层原理是基于Netty的网络通信,java服务和FreeSwitch关系是一对多。因为FreeSwitch有个自我保护机制会限制每秒的并发量,所以java这边必须要限流,java采用了线程池的方式限流,下发呼叫orignate命令到线程池,负载均衡轮询到FreeSwitch。

二、 生产现象
运维团队告警发现一台FreeSwitch宕机了,随后告警外呼系统已经不在进行呼,现线程池线程已经不再继续消费。

三、 紧急处理
通过jps命令,当前Java服务一直存在,并且成功接收其他服务的http请求并200成功响应,排查log日志发现接口请求都是正常的,但是加入到线程池之后,后面线程消费的日志就没了。宕机的FreeSwitch,watchDog发现之后重新拉起了。当前的esl重连也是正常的。
通过top、free命令发现当前服务器的cpu及内存都处于较低的水平(1%左右),线程池中的消费线程没有日志输出,而且没有任何错误日志,java线程池不消费了。
为了尽快修复生产环境,通过jstack pid 、jmap -heap手动保存线程堆栈和内存堆栈信息,然后手动重启服务,生产环境恢复。

四、问题排查
1、 跟踪日志和业务代码没有发现任何问题,此时猜测因为某种原因导致线程异常退出。查看jstack命令输出的文件,发现线程池的消费线程一直处于阻塞状态,继续跟踪esl的源码,发现请求之后会调用get()方法获取对应的响应值,get()中有一个CountDownLatch.await() 一直在等待FreeSwitch响应,FreeSwitch正常响应的时候会执行CountDownLatch.countDown()释放锁,但是此时的FreeSwitch已经挂了,没有办法响应导致线程池的线程一直在await()
2、 增加线程uncatch异常输出,方便后续日志排查,在测试环境进行模拟,希望可以复现当时的现象,模拟外呼数据量及外呼并发比较大的时候导致FreeSwitch宕机的现象,经过大量时间的压测之后复现了当时的现象。

最终确认就是因为CountDownLatch.await()导致线程阻塞,导致线程池不消费的问题,解决方式:修改源码await()加一个超时时间,超时之后释放锁。

总结
一、java线程池不消费原因总结:

1、因为java.lang.OutOfMemoryError: unable to create new native thread原因导致线程异常结束,其他的error或者exception也会导致线程异常结束,就需要增加线程uncatch异常输出,方便后续问题排查。
2、因为IO操作导致线程一直阻塞状态,要充分排查线程中的网络IO、磁盘IO、数据IO,内存IO等会导致阻塞的操作,尤其是死锁或者CountDownLatch。

二、Java线程池使用总结:

1、线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
*newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
*newCachedThreadPool和newScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
2、增加线程uncatch异常输出,方便后续日志排查,以及出现问题后的补偿操作,比如可以继续运行此线程等,也可以根据各自的业务需求特殊处理。
3、可以通过 getTaskCount()、getCompletedTaskCount()、getPoolSize()、getActiveCount()等方法监控线程池的状态。
4、可以自定义拒绝策略executor.getQueue().put®,实现阻塞型线程池,解决oom和线程池原生拒绝策略消息丢失的问题。
5、可以自定义阻塞队列,实现二级线程池,即优先一级线程池,一级线程池满了之后优先二级线程池,然后在放入阻塞队列中。
6、可以自定义实现相同key的消息串行,不同key的消息并行的亲缘线程池。
7、可以参考Hystrix,采用线程池实现限流。
8、可以参考tomcat线程池,自定义线程阻塞队列,以调整线程池执行顺序,比如优先核心线程数>最大线程数>线程阻塞队列>拒绝策略。

三、配置线程池需要考虑因素:

CPU密集型:尽量使用较小的线程池,一般Cpu核心数+1
IO密集型 :
1、可以使用较大的线程池,一般CPU核心数 * 2
2、(线程等待时间与线程CPU执行时间之比 + 1)* CPU数目
混合型:可以将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,根据业务情况而定(执行时间差别较小,拆分为两个线程池;否则没有必要拆分)

线程池究竟设置多大要看你的线程池执行的什么任务了,CPU密集型、IO密集型、混合型,任务类型不同,设置的方式也不一样。从任务的优先级,任务的执行时间长短,任务的性质(CPU密集/ IO密集),任务的依赖关系等角度来分析。并且近可能地使用有界的工作队列。最终的线程参数还是要根据实际业务的压测结果来定。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值