线上故障分析 Socket accept failed java.io.IOException: Too many open files 导致的系统宕机问题
1、项目运行环境, CentOS Linux release 7.4.1708,springboot 2.1.0.RELEASE,jdk1.8,mysql 5.6.16
2、系统应用,在这里简称为应用A,在某个周末的时候,无法正常运行,连续抛几十分钟异常java.sql.SQLNonTransientConnectionException: Could not create connection to database server,导致系统宕机,截图如下:
3、最开始以为是连接不上数据库,当时telnet ip port,发现mysql数据库服务端网络也是正常的;
是否数据库连接池的设置问题,然后去看了一下连接池配置,需要增大吗,是否越大就越好?可以参考这篇博客:
https://blog.csdn.net/educast/article/details/93461372(数据库连接池的大小值设定,不能随意设置太大的值)
看完以后,发现系统设置的初始化大小:5,最大:20,好像并没有什么问题
4、是不是系统在某个时间点,由于网络原因,连接不上服务了,解析域名的时候报错,当网络恢复以后无法正常连接?发现以下连接设置,有autoReconnect=true参数,当网络恢复的时候,会自动进行数据库重连接,也不至于导致系统宕机。
spring.datasource.url=jdbc:mysql://*************.mysql.rds.aliyuncs.com:3306/**********?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&autoReconnect=true
下面验证一下是否会进行自动重连,在测试环境配置一个数据库访问域名,先断开网络,报异常如下:
恢复网络,发现预编译sql都打印出来,并且执行,说明autoReconnect=true参数是有作用的,并且不会由于网络临时抖动问题,导致数据一直抛异常:java.sql.SQLNonTransientConnectionException: Could not create connection to database server
5、在发生宕机的同一台服务器上,我们还有另外一台应用B;A应用和B应用部署在同一台服务器上,用的同一数据库连接配置,这种宕机的问题,发生过两次,都在A应用,同时间B应用却没有找到这样的异常日志,我们再把这个异常日志分析一下:
在SQLNonTransientConnectionException异常发生之前,有很多 Socket accept failed
java.io.IOException: Too many open files
那么数据库连接异常 和 Too many open files有什么关系呢?
在linux环境下,在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件:普通文件,目录,NFS文件,字符文件,管道,Socket流,网络Socket等等,并且可以通过lsof命令,查看系统打开文件大小,或者某个pid占用文件大小;使用ulimit命令,查看默认分配占用文件大小;
lsof -p pid | wc -l,查看pid进程打开了多少个文件句柄:
关于上面所说的可以参考如下博客链接:
参考连接,lsof(list open files)命令:https://blog.csdn.net/qq_27870421/article/details/92803453
limit资源限制,ulimit命令 详解:https://blog.csdn.net/skiwnc/article/details/84100095
6、并且在lsof命令,查看到很多/data1/hrbb/file/temp的占用,一共700多
应用程序启动路径是在/data1/hrbb/下面,所以程序中有很多占用路径/file/temp的资源?,那么去代码中找一下是否有file/temp资源使用的地方呢?
找到一处localPath变量:
7、发现文件资源,使用完,没有关闭,导致大量/data1/hrbb/file/temp 占用文件句柄;
并且在finally里面的delete()方法,由于没有关闭流,也是删除不掉文件的(这一点已经在本地写测试代码,验证过了,就不贴出来了);
然后我们去看一下这里代码的提交记录对比,左边代码是之前原始版本,右边是误修改后的版本
try(){}catch{},和 try{}catch{}是不一样的(注意第一个try语法后面的小括号)
原来是用了 try-with-recourse 语法;
8、所以问题大概就出在文件使用资源上面,再从日志里面搜一下,Too many open files第一次出现的地方
grep -C 200 'Too many open files' ***.log | more
file/config/EcspPublicKey.cer证书文件,无法读取;那么我们再去看代码,是否还有其它使用文件资源的地方,没有释放呢?
9、在IDEA中继续全局搜索 FileInputStream使用地方:
找到demo代码如下,168~174行被修改过,这里的InputStream和OutPutStream都没有关闭,
10、Java库中有很多资源需要手动关闭,比如InputStream、OutputStream、java.sql.Connection等等。在此之前,通常是使用try-finally的方式关闭资源;Java7之后,推出了try-with-resources声明来替代之前的方式。 try-with-resources 声明要求其中定义的变量实现 AutoCloseable 接口,这样系统可以自动调用它们的close方法,从而替代了finally中关闭资源的功能;
我在本地写了一段测试代码,在try()中声明IO流对象,并没有手动关闭资源,打包后反编译class文件,可以看到编译器识别到了语法糖,自动加上了资源释放代码:
想必修改的人,可能不知道这个语法,而且也忽略了关闭资源,导致了资源泄漏的问题;
关于 try-with-recourse 详细语法细节问题,可以参考帖:
11、然后回到代码继续找,没有关闭资源的地方:
这里,关闭了ObjectInputStream,却没有关闭FileInputStream :
还有这里,如果抛异常,也会导致资源无法关闭:
12、回到应用服务器上,ulimit -a,查看单个pid允许最大文件数:1024
之前抛异常的应用程序,pid为26562 ,目前已经达到557,昨天看还是400多;
希望下次出故障的时候,能再分析,确认一下是这个文件的问题;
修复方式:更改代码,finally中,释放资源,或者用try with resource方式。
上线待验证,后续是否还会出现此类问题
13、在另一天程序中出现的同样问题:
更多推荐
所有评论(0)