• 记一次线程爆满导致服务器崩溃的问题排查


    记一次线程爆满导致服务器崩溃的问题排查

    重启服务器

    • 重启后,ssh连接发现下面问题

    • fork faild:Cannot allocate memory

    • 在这里插入图片描述

    • 以为是内存满了

    • 于是,free -h,查看内存情况,还有,观察一段时间后,内存没多大变化

    • 在这里插入图片描述

    修改最大线程数

    • 经过各种百度,都说可以通过修改服务器的最大线程数来解决,于是我也这么干了。当时做的时候没有截图,所以下面截图是网上找的,凑合看看。

    • 查看最大进程数 sysctl kernel.pid_max

    • 在这里插入图片描述

    • ps -eLf | wc -l查看 进 程数

    • 修改最大 进 程数后系统恢复

    • echo 1000000 > /proc/sys/kernel/pid_max
      
      • 1
    • 永久生效

    • echo "kernel.pid_max=1000000 " >> /etc/sysctl.conf
      sysctl -p
      
      • 1
      • 2

    查找线程最大的java程序

    • 上一步扩大了线程数量后,感觉有点不对,因为之前没有这么配置都可以正常运行,为什么突然服务器挂了呢?肯定是有程序在作怪。
      于是决定找出占用线程最多的程序。回顾最近几天,服务器中只部署了几个springboot程序。问题一定出在它们之中。

    • 查看线程数量前20的java程序

    • ps -Lef |awk ‘{sum[$2]++}END{for(pid in sum) print pid, sum[pid]}’|sort -nr -k 2|head -n 20
      
      • 1
    • [root@se-test-lky01 ~]# ps -Lef |awk '{sum[$2]++}END{for(pid in sum) print pid, sum[pid]}'|sort -nr -k 2|head -n 20
      16074 3100
      31386 1226
      20120 1072
      19548 985
      9697 829
      3005 796
      641 344
      19016 324
      16924 315
      17870 300
      6417 293
      8351 171
      7332 168
      18259 167
      19821 161
      16311 157
      18433 151
      18048 136
      14347 104
      2559 100
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    • 观察一段时间后,发现进程id为16074的java程序的线程数不断增长。

    导出问题程序的线程日志

    • [root@se-test-lky01 ~]#jstack 16074 >thread_dump.log
      
      • 1
    • 分析日志,发现下面情况,线程数量不断增加,代码位置在FtpMonitorProcess.java:85

    • "Thread-4655" #4774 prio=5 os_prio=0 tid=0x00007f84aa2fe000 nid=0xd408b waiting for monitor entry [0x00007f802b704000]
         java.lang.Thread.State: BLOCKED (on object monitor)
      	at cn.cloudwalk.bat.util.http.FtpUtil.connect(FtpUtil.java:246)
      	- waiting to lock <0x00000006c09c1888> (a java.lang.Class for cn.cloudwalk.bat.util.http.FtpUtil)
      	at cn.cloudwalk.bat.schedule.ftp.process.FtpMonitorProcess$1.run(FtpMonitorProcess.java:85)
      	at java.lang.Thread.run(Thread.java:748)
      
      "Thread-4654" #4773 prio=5 os_prio=0 tid=0x00007f84aa2fc000 nid=0xd408a waiting for monitor entry [0x00007f802b805000]
         java.lang.Thread.State: BLOCKED (on object monitor)
      	at cn.cloudwalk.bat.util.http.FtpUtil.connect(FtpUtil.java:246)
      	- waiting to lock <0x00000006c09c1888> (a java.lang.Class for cn.cloudwalk.bat.util.http.FtpUtil)
      	at cn.cloudwalk.bat.schedule.ftp.process.FtpMonitorProcess$2.run(FtpMonitorProcess.java:114)
      at java.lang.Thread.run(Thread.java:748)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

    找到问题代码

    • 发现这个方法每次被调用就会创建一个新的线程。而这个方法是被定时任务调用的,每10秒调用一次。

    • 问题就出在ftp没有配置,所以线程内执行ftp操作时,线程阻塞,没能释放。若ftp可用,则不会出现线程阻塞问题。

    • 这就是问题根源。

    • 	private void listDeviceFiles() {
      		
      		new Thread(new Runnable() {
      			@Override
      			public void run() {
      				logger.debug("开始获取[ftp-设备]文件...");
      				try {
      					String workDir = ftpConfig.getWorkdir();
      					// 连接
      					FTPClient ftpClient = FtpUtil.connect(ftpConfig);
      					ftpClient.changeWorkingDirectory(workDir);
      					ftpClient.changeWorkingDirectory(SubscribeDataTypeEnum.DEVICE_INFO.getKey().toString());
      					FTPFile[] files = ftpClient.listFiles();
      					for(FTPFile file : files) {
      						decomposeFile(file,ftpClient);
      					}
      					ftpClient.logout();
      				} catch (Exception e) {
      					logger.error("ftp获取文件名出错:" + e.getMessage());
      				}
      			}
      		}).start();
      	}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23

    解决方案

    • 不建议手动创建线程,改用使用线程池。
  • 相关阅读:
    oracle varchar2类型如何转化为date类型
    Web攻防01-ASP应用相关漏洞-HTTP.SYS&IIS短文件&文件解析&ACCESS注入
    6月27日云技术研讨会 | 中央集中架构新车型功能和网络测试解决方案
    资源释放的方式(try - with - resource 和 try - catch - finally)
    Java 日志框架,性能无敌横扫所有对手
    【socket.js联合express】:搭建简约版聊天室
    pandas DataFrame内存优化技巧:让数据处理更高效
    机器学习 —— 线性回归 简单使用
    【Linux集群教程】14 集群装机 - PXE原理和PXE服务搭建
    C++快速理解之面向对象
  • 原文地址:https://blog.csdn.net/Andrew_Chenwq/article/details/133999884