项目背景
一个物联网平台项目,主要有两类设备接入进来,一类是直连设备,这个设备数量大概在 100 多台左右。另外一类是通过第三方平台以接口的方式接入进来,这类设备的数量大概在 600 台左右。
问题描述
发布生产后每隔几周就会出现下面的错误信息。
java.net.SocketException: Too many open files
这个问题我看网上很多解决方式是调整服务器默认的最大可打开文件数量,就是使用
ulimit -n
。这种方式可能对于有些情况是有用的, 但是对于我当前的这种情况是治标不治本。因为当我执行ls /proc/程序pid/fd | wc -l
发现程序打开的 open files
数量已经达到了 4000 多,所以可以得出是出现了文件句柄泄漏的问题。
问题追踪
既然确认了问题,那我们接下来就开始排查具体是什么原因导致了文件句柄泄漏,一般出现文件句柄泄漏主要是打开的资源没有正常的关闭。忘记关闭的资源类型主要有以下几种:
- 网络套接字 (Socket, ServerSocket, DatagramSocket)
- 文件流 (FileInputStream, FileOutputStream, FileReader, FileWriter, RandomAccessFile)
- 数据库连接 (java.sql.Connection, Statement, ResultSet) - 这些底层通常也使用套接字或文件。
- 压缩流 (ZipInputStream, ZipOutputStream) 根据当前业务场景可以确定出现资源泄漏的类型大概率是网络套接字这块(当前业务基本没有打开文件的操作,数据库使用到了连接池),其实根据持续观察打开的文件类型也能判断出持续增加的 都是 socket 类型的。
哪里的连接出现了泄漏问题?
当前服务是使用 nginx 做了负载均衡,服务分别运行在 A,B,C 三台服务器上面,这里只有 A 服务器上的服务是直连了设备,也只有 A 服务器上才出现了泄露的问题。
所以问题大概率就出现在设备直接服务器这里的处理。我们现在可以查看一下整个服务 tcp 服务链接情况,可以使用netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
<img
src=”https://img.newjuncai.com/img/tcp_status.png”
alt=“TCP 状态描述”
/>
可以看出基本都是正常建立的连接,这里我开始一直在怀疑是不是 Netty 作为 Server 中间的一些处理不当导致了,然后排查了相关代码发现都是把对应的资源关闭了的,结合当前的这套代码之前一直运行是正常的。
所以我把排查方式改成直接看关于直连设备连接管理的最近修改上面,最后排查到代码是下面这一段
<img
src=”https://img.newjuncai.com/img/error_code.png”
alt=“存在泄漏的代码”
/>
问题处理
结合日志看这里自从把state == IdleState.READER_IDLE
删除掉后就再也没有出现过设备心跳超时的日志了,也就是说如果设备开始连接到服务器成功后则服务器就会一直认为这台设备是在线的,但是
实际情况是设备可能由于某些网络问题掉线后重连,重连后就会出现新的连接,旧的连接管道一直处于开启的状态没有关闭,随着时间的推移打开的文件句柄就会一直增加。这里我们把代码恢复后发版观察了两个
小时发现打开的连接数一直保持在 680 左右,没有像之前那样出现一直增长的情况。
总结
这次的问题主要还是由于修改一个业务问题时导致了新的 Bug,由于接入有低频上报的设备,大概是一个小时才上报一次,但是之前 READER_IDLE 时间很短就会直接把设备改成下线状态,其实它是正常在线的,当时很草率的就把这个关键代码给删除了。主要还是当时没有完全理解IdleState.READER_IDLE
代表的含义,知道问题所在后,其实要解决低频设备误下线问题,我们可以把 READER_IDLE 的监测时间调长一点就可以解决。