问题背景

线上项目在执行 ftp 下载的时候报如下错误

  1. java.net.SocketException: Too many open files
  2. at java.net.Socket.createImpl(Socket.java:460)
  3. at java.net.Socket.getImpl(Socket.java:520)
  4. at java.net.Socket.setSoTimeout(Socket.java:1141)
  5. at org.apache.commons.net.ftp.FTPClient._openDataConnection_(FTPClient.java:917)

看报错原因:是该服务(某个进程)打开了超过系统设置的文件句柄数量

问题定位

首先找到报错的这一台服务器
然后使用命令查看文件句柄配置

  1. [~]# ulimit -a
  2. core file size (blocks, -c) 0
  3. data seg size (kbytes, -d) unlimited
  4. scheduling priority (-e) 0
  5. file size (blocks, -f) unlimited
  6. pending signals (-i) 127906
  7. max locked memory (kbytes, -l) 64
  8. max memory size (kbytes, -m) unlimited
  9. open files (-n) 1024 # 看这里
  10. pipe size (512 bytes, -p) 8
  11. POSIX message queues (bytes, -q) 819200
  12. real-time priority (-r) 0
  13. stack size (kbytes, -s) 8192
  14. cpu time (seconds, -t) unlimited
  15. max user processes (-u) 127906
  16. virtual memory (kbytes, -v) unlimited
  17. file locks (-x) unlimited

可以看到,该配置是默认的 1024 配置,比较小,我们先来确定到底是哪一个进程导致的

首先在本例中,明确的知道是哪一个进程(服务)报错的,就不用下面这一个步骤了

  1. [~]# lsof | awk '{print $2}' | sort | uniq -c | sort -n -r | head -n 10
  2. 1404071 93486
  3. 42066 2014
  4. 37449 103586
  5. 25414 65935
  6. 20064 1898
  7. 11978 32574
  8. 6956 110448
  9. 435 1281
  10. 306 764
  11. 258 762

上述命令目前笔者不清楚第一列是什么意思,第二列是进程号,是按当前系统中打开文件句柄数量倒序排序的。

查看这个进程目前打开的句柄详细信息

  1. lsof -p 93486
  2. # 或则使用如下命令将信息导入到文件中
  3. # lsof -p 93486 > openfiles.log
  4. # 然后使用命令查看详细信息:N 显示行号,m 显示百分比
  5. # less -N -m openfiles.log
  6. java 93486 root 804r REG 8,17 171061964 34342129 /xxx/temp/ce48058c1924476fb392c20a60fa4bb2-20210311175647.txt (deleted)
  7. java 93486 root 805r REG 8,17 6354790 34342150 /xxx/temp/b61f85f794ab4dd2847061daaf2d43fb-20210311175756.txt (deleted)
  8. java 93486 root 806r REG 8,17 168412335 34342151 /xxx/temp/627ba21b01da435eabeaeda35f7f441c-20210311175929.txt (deleted)
  9. java 93486 root 807r REG 8,17 12839463 34342167 /xxx/temp/77755adb5271453da1fb53caf945c2f8-20210311180159.txt (deleted)
  10. java 93486 root 808r REG 8,17 168410493 34342168 /xxx/temp/3d8e65f13ea14a0e9c95bddab5b99bc9-20210311180312.txt (deleted)
  11. java 93486 root 809r REG 8,17 478 34342221 /xxx/temp/e9b11e6ab6034cf8bffb218c38803bab-20210302220706.txt (deleted)
  12. java 93486 root 810r REG 8,17 5639576 34342186 /xxx/temp/d8d2d7938a994e3a94236fecefd54135-20210311180836.txt (deleted)
  13. java 93486 root 811r REG 8,17 168399280 34342194 /xxx/temp/cc7d5c81d41f4d9080197aedf8a44f70-20210311180949.txt (deleted)

通过分析(使用文件的方式打开,可以显示行号),可以看到这个进程打开了 4000+ 的句柄,如上所示,大部分都是打开了一个文件。
所以这里猜测是代码中,要么是读或则是写之后没有正确的释放这个资源导致的。

解决方案

通过如上定位,首先是打开了那么多的临时文件,根据文件名称判断,好一两个月前的都还没有被释放,并且这个文件都已经被删除过了,那么一定是代码有问题,那么现在的解决方案有两个:

  1. 先临时修改单个进程的打开文件句柄数量,重启服务后,先恢复服务
  2. 然后去排查代码

修改打开文件限制数量

临时修改最大文件打开数 ulimit -n 修改数

  1. ulimit -n 65535

永久生效需要修改配置文件 /etc/security/limits.conf

  1. * - nofile 65535
  2. # 或则分别写
  3. * soft nofile 60000
  4. * hard nofile 65535

有可能需要重新启动,所以,先临时修改,再修改配置文件,等可以重启机器的时候再重启

代码排查

通过打开的文件内容,基本能确定是在某一块业务相关的代码,由于这一块的业务逻辑有点复杂,代码量也较多,找了很久都定位不了到底是哪里没有释放资源

lsof 的其他使用

查看哪个进程占用了文件

  1. [~]# lsof /xxx/temp/44982c03646d4c97a5b47808dc3988a6-20210507180004.txt
  2. COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
  3. java 50958 root 385r REG 8,17 118009 34341699 /xxx/temp/44982c03646d4c97a5b47808dc3988a6-20210507180004.txt