当前位置: 首页 > 后端技术 > Java

解决Java线程过多问题的方法——用Arthas解决Jenkins线程飙升的问题

时间:2023-04-01 19:50:10 Java

0。持续集成是可能的。同时,Jenkins提供了大量的各种插件来满足用户持续集成的需求。比如Jenkins提供的influxdb插件,可以将构建执行步骤、耗时、结果等数据发送到influxdb数据库,方便后期对构建数据进行分析和展示。Jenkins广泛应用于公司内部各类项目的持续集成,支持3000+项目,每天近万次构建。Jenkins是CI/CD的核心环节和重要环节,保证Jenkins的高可用和高性能显得尤为重要。一、症状我们的Jenkins服务运行一段时间后会异常卡死,严重降低持续集成速度,影响研发工作效率。问题出现后,我们第一时间查看了Jenkins监控仪表盘。从监控dashboard可以看到JVM线程数猛增,达到20K:2.问题分析2.1Dumpthreadstack发现问题后,上Jenkins机器,dumpjvm的threadstack。#获取Java进程idjps-l19768/home/maintain/jenkins-bin/jenkins/jenkins.war#dumpthreadstackjstack19768>jstack.txt2.2analyzethreadstack得到这个dumped的线程栈,我们使用https://fastthread.io/这个网站,分析jvm线程栈。大致结果如下:TotalThreadscount:20215ThreadGroup:RxNewThreadScheduler18600threads从上面的信息我们可以知道jvm一共有20215个线程,其中18600个是线程组RxNewThreadScheduler创建的线程。2.3定位线程的来源在JVM的线程栈中,出现了大量的RxNewThreadScheduler线程组。从字面上看,猜测应该是RxJava相关的线程。为了验证这个猜测,我们决定去查一下RxJava框架的源码,看看RxNewThreadScheduler线程是不是由RxJava框架生成的。在GitHub上rxjava的源码中搜索RxNewThreadScheduler,如下:代码:https://github.com/ReactiveX/...结果:确实,RxJava项目中包含一个线程池,其线程名前缀为RxNewThreadScheduler,并且代码在NewThreadScheduler类中,证实了我们的猜测。3、解决方案3.1排查思路经验证RxNewThreadScheduler线程名属于RxJava,极有可能是RxJava导致线程数暴涨的问题。问题是RxJava与Jenkins有什么关系?Jenkins的一个插件是不是引入了RxJava?这个问题好像没办法排查:我们的jenkins里面装了几十个插件,一个一个看源码不仅费时费力,而且还不一定能行:Jenkins插件的源码不一定直接写引用RxJava。我们只知道一个线程名称和它所属的应用程序RxJava。如何定位这个问题是从哪里引入的呢?从线程的dump信息来看,基本没有价值:"RxNewThreadScheduler-2"#4079daemonprio=5os_prio=0tid=0x00007fa2402a1000nid=0x5eafwaitingoncondition[0x00007fa12a9ae000]java.lang.Thread.State:TIMED_WAITING(parking)在sun.misc.Unsafe.park(NativeMethod)-在java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)在java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)在java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:10793).ScheduledThreadDelayPoolExecutor($ScheduledThreadPoolExecutor.java:809)在java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)在java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)atjava.lang.Thread.run(Thread.java:748)故障排除之路似乎be不能继续:疑惑无路,换位思考。既然问题是RxJava引入的,那我们看看Jenkins是怎么把这个RxJava加载进去的呢?毕竟RxJava的相关代码最终会运行在Jenkins对应的JVM中。有没有什么工具可以方便直观的查看JVM加载的类和jar包信息?Arthas提供了方便快捷的工具。3.2Arthas简介引用Arthas官网https://arthas.aliyun.com/doc...的介绍:Arthas是阿里巴巴开源的Java诊断工具,非常受开发者欢迎。Arthas可以帮助解决以下问题:这个类是从哪个jar加载的?如果遇到问题不能在线调试,难道只能加日志重新发布吗?如何直接从JVM中找到一个类的实例?当然,arthas能解决的不仅仅是上述问题。有关详细信息,请参阅官方文档。这里的第一个问题正是我们遇到的问题。我们需要知道是哪个jar包加载了RxJava相关的类。3.3解决方案——ArthasClassloader我们使用arthas来帮助排查问题(官方文档中有arthas的安装方法,这里不再赘述)。Arthas提供了查看类加载相关信息的功能:classloader-l。java-jararthas-boot.jarclassloader-l|tee/home/shared/log/arthas.log从arthas的输出中找到了RxJava:可以看到,RxJava是由influxdb插件引入的。注:引入influxdb是为Jenkins构建数据统计。没想到这个陷阱。考虑改用prometheus来收集数据。此时的感觉是:柳暗花明。3.4问题解决知道问题是influxdb插件引入后,我们先禁用influxdb插件,重启Jenkins。稳定运行一段时间后,观察Jenkins的线程数:可以看到Jenkins的线程数稳定在1K左右,没有暴涨。同时查看Jenkins任务的构建状态,已经恢复到正常水平,没有任何滞后和延迟。4.源码及根因分析Jenkins中引入了influxdb插件,用于存储和分析Jenkins构建的作业数据。为什么influxdb插件会导致Jenkins线程数飙升?这个问题的根本原因取决于插件的源代码。4.1Influxdb上报统计数据JenkinsJob构建时,influxdb插件会通过HTTP请求将统计数据存储在influxdb数据库中。Influxdb插件使用OkHttp+RxJava完成HTTP请求。下面将分析influxdb插件向influxdb数据库上报统计数据的关键流程源码:Jenkins每次build完成后,influxdb插件会调用writeToInflux方法上报相应的数据,如图下图:获取influxdb写的api,并写入通过api发送统计数据的关键是写API的配置:WriteOptions.DEFAULTS。我们来看看它的具体配置:关键是RxJava中提供的I/O线程调度器Scheduler。它的实现是Schedulers.newThread(),对应的代码如下:在Schedulers.newThread()方法中,看到了RxJava的身影,真正的处理逻辑交给newThreadScheduler处理:在初始化newThreadScheduler,创建一个NewThreadTask,真正的线程Handle逻辑交给他。4.2NewThreadScheduler调度器线程模型先来看NewThreadTask的定义:staticfinalclassNewThreadHolder{staticfinalSchedulerDEFAULT=newNewThreadScheduler();}staticfinalclassNewThreadTaskimplementsCallable{@OverridepublicSc??hedulercall()throwsException{returnNewThreadHolder.DEFAULT;可以看出该类实现了Callable接口并重写了call方法,所以在真正执行的时候会调用该类的call方法,而在call方法中,返回的scheduler就是调度器NewThreadScheduler设备.而NewThreadScheduler这个类恰好就是我们在GitHub上搜索线程名RxNewThreadScheduler时出现的那个。NewThreadScheduler调度器的核心代码:在这里,我们看到influxdb是如何关联RxNewThreadScheduler线程池的:THREAD_FACTORY=newRxThreadFactory("RxNewThreadScheduler",priority)。NewThreadScheduler,调度器,在真正执行工作的时候会创建一个NewThreadWorker。其核心代码如下:NewThreadWorker使用的线程池最后创建了一个数量特别大(Integer.MAX_VALUE)的最大线程池,队列大小为16个线程池。当JenkinsJob构建量猛增时,influxdb的写入量也猛增,而influxdb使用的IO线程调度器RxJava对创建的线程池几乎没有上限,导致写入量高时创建influxdb.线程数也多,最终导致Jenkins线程数暴涨。5、Jenkins数据统计新方案目前使用influxdb插件做数据统计会遇到大量Job构建时线程数暴涨的问题。使用influxdb进行数据统计并不是唯一的选择。Prometheus是业界成熟且通用的解决方案。我们考虑以后把数据统计切换到prometheus。6、感觉这次排查的唯一线索就是线程名RxNewThreadScheduler,所以在创建线程池的时候一定要选择一个比较好的名字。解决问题的同学会非常感谢你;创建线程池,一定要记得控制maxPoolSize和queueSize,不要创建无限线程池;工欲善其事,必先利其器。掌握Arthas等利器,能够快速定位和解决问题。我叫梅小希,最近在一家东南亚电商公司从事DevOps方面的工作。从本期开始,我们会继续分享基于Jenkins的CI/CD工作流程,包括JenkinsOnk8s等,如果对Java或者Jenkins感兴趣的可以联系我:wxweven(注意DevOps),公众号:这个文章由博客群发等运营工具平台OpenWrite发布