事故现场

服务在运转一段时间后,体系监控中load目标飙升到峰值,频频呈现请求失利告警,最终服务因无法建立新的socket衔接,导致服务雪崩。必须经过重启服务的办法才干康复正常。

问题定位

刚开始以为是oom,可是没有发现oom日志,而且仓库运用和gc情况都在正常的范围内。然后在服务日志发现很多的错误信息:Too many open fils(或 翻开的文件过多)。

Too many open files:句柄数超过体系约束,也是Linux体系中常见问题,这里的 files 不仅是体系文件,还包含请求衔接 socket,端口监听等。

发生原因

经过排查后确定是因为在加载暂时数据文件后删除文件没有封闭stream释放句柄,所以服务在运转一段时间后呈现 Too many open files。

句柄原理

在Linux体系中,目录、字符设备、块设备、套接字、打印机等都被抽象成了文件,即一般所说的“全部皆文件”。程序操作这些文件时,体系就需求记载每个当前拜访file的name、location、access authority等相关信息,这样一个实体被称为file entry。这些实体被记载在open files table中,Linux体系配置了open files table中能容纳多少file entry。假如超过这个配置值,则Linux就会回绝其他文件操作的请求,并抛出Too many open files。

处理进程

(1)检查体系句柄约束数

经过 ulimit -a 命令,能够看到 open files 便是允许单个进程翻开的最大句柄数,默许是1024 。

[root@kvm-10-12-10-55 ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 31211
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 31211
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

(2)找到服务进程PID,检查该进程已翻开的文件句柄。

[root@kvm-10-12-10-55 ~]# ps -ef | grep syx-service
root     30118 25539  0 15:51 pts/3    00:00:21 java -jar syx-service-0.0.1-SNAPSHOT.jar
[root@kvm-10-12-10-55 ~]# lsof -p 30118 | wc -l
1024
[root@kvm-10-12-10-55 ~]# lsof -p 30118
java    30118 root *925r      REG              253,0        26     408780 /tmp/a01.txt (deleted)
java    30118 root *926r      REG              253,0        26     408780 /tmp/a02.txt (deleted)
java    30118 root *927r      REG              253,0        26     408780 /tmp/a03.txt (deleted)
java    30118 root *928r      REG              253,0        26     408780 /tmp/a04.txt (deleted)
java    30118 root *929r      REG              253,0        26     408780 /tmp/a05.txt (deleted)
java    30118 root *930r      REG              253,0        26     408780 /tmp/a06.txt (deleted)
java    30118 root *931r      REG              253,0        26     408780 /tmp/a07.txt (deleted)
java    30118 root *932r      REG              253,0        26     408780 /tmp/a08.txt (deleted)
java    30118 root *933r      REG              253,0        26     408780 /tmp/a09.txt (deleted)
java    30118 root *934r      REG              253,0        26     408780 /tmp/a10.txt (deleted)
...

(3)正常情况下,能够经过增大 open files 的办法来处理这个问题,可是我所呈现问题采用这个办法,是不能彻底处理的,只能定位到问题代码来处理。

  • 暂时修正文件句柄数约束参数
ulimit -n 2048 # 重启后会康复默许值,非root用户只能设置到4096
  • 经过修正配置文件永久修正
[root@icoolkj ~]# vi /etc/security/limits.conf
# 文件末参加
* soft nofile 655360
* hard nofile 655360

一次因 Too many open files 引发的服务雪崩

(4)定位问题代码。

经排查服务日志发现,定位到代码是 long count = Files.lines(Paths.get(path)).count(); 没有封闭stream导致。

Files.lines()的运用办法在官方文档中是这么说的:

If timely disposal of file system resources is required, the try-with-resources construct should be used to ensure that the stream’s close method is invoked after the stream operations are completed.

原因

  • Files.lines() 这个办法,没有封闭翻开的文件
  • 假如需求周期性的读取文件,需求运用try-with-resources语句来确保stream的close办法被调用,然后封闭翻开的文件。

处理

修正为下面写法,即可处理啦。

try(Stream<String> stream = Files.lines(Paths.get(path))){
    count = stream.count();
} catch (IOException e){
    e.printStackTrace();
}

等价于

Stream<String> stream = null;
try{
    stream = Files.lines(Paths.get(path));
    count = stream.count();
}catch (IOException e){
    e.printStackTrace();
}finally {
    if(stream != null){
        stream.close();
    }
}

以上,希望对遇到同样问题的小伙伴有协助。

相关链接:<Files.lines()办法运用相关问题> blog.51cto.com/u_15185289/…


最终,祝我们圣诞快乐,Merry Christmas !!!

一次因 Too many open files 引发的服务雪崩