Gateway 报 Too many open files 错误的分析及解决办法
一、bug描述
最近组里上线一个项目,该项目以spring-boot/spring-cloud等技术来架构,网关使用的是spring cloud gateway。在测试环境中,我们进行过长时间的测试,并没有发现任何问题,发布到idc后也没有太大问题。
发生问题的时候,我们刚好上线了几个控制端,这些控制端有个共同的特点,那就是他们会定时的发送心跳给服务网关。一下子并发数上来以后,gateway突然报 Too many open files的错误。
下面摘取一段报错的主要信息,供大家参考:
2021-01-20 15:53:18.528 WARN [open-platform-gateway,,,] 1 --- [or-http-epoll-1] io.netty.channel.DefaultChannelPipeline : An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.channel.unix.Errors$NativeIoException: accept(..) failed: Too many open files
2021-01-20 15:53:19.528 WARN [open-platform-gateway,,,] 1 --- [or-http-epoll-1] io.netty.channel.DefaultChannelPipeline : An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.channel.unix.Errors$NativeIoException: accept(..) failed: Too many open files
2021-01-20 15:53:20.528 WARN [open-platform-gateway,,,] 1 --- [or-http-epoll-1] io.netty.channel.DefaultChannelPipeline : An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.channel.unix.Errors$NativeIoException: accept(..) failed: Too many open files
2021-01-20 15:53:21.528 WARN [open-platform-gateway,,,] 1 --- [or-http-epoll-1] io.netty.channel.DefaultChannelPipeline : An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.channel.unix.Errors$NativeIoException: accept(..) failed: Too many open files
二、分析Bug
按照多年的开发经验,Too many open files 的错误应该是打开的文件太多又没有及时关闭,或者没忘记关闭的原因。因此我们确定两个方向进行排查:
- 1、查看是否网关的代码里面频繁的打开文件但没有正确关闭。
- 2、查看log,除了
Too many open files
的错误以外,是否还存在IOException的异常。
我们当时顺着上面两个思路进行排查后并没有任何收获。那么问题究竟在哪里呢?
让我们回到bug log的原文上,多次出现一小段logio.netty.channel.unix.Errors$NativeIoException: accept(..) failed: Too many open files
引起了我们的注意。
通过字面意思,我们能够从那一段log的上轻松地得到以下几个信息:
- 1、gateway 使用了netty框架进行开发。
- 2、Too many open files 的错误发生在 accept 阶段。
- 3、NativeIoException 表示错误发生在代码的native层。
接下来我们逐一分析,通过抽丝剥茧的分析来得到一个大概的排查方向。
首先,gateway 使用了netty框架,意味着网关的并发量不会太弱,起码单机并发量在一万数量级以上,具体看业务接口的设计。那么会不会是因为并发量超出了网关的承受能力?通过tcpdump抓包统计,观察到我们的并发量只有100左右的数量级,远远没有超出netty框架的承受能力。因此,我们排除因为并发问题导致的异常。
接下来我们来看下第二点信息。该异常发生在accept阶段,而且信息显示accept fail,说明网关在接受外部请求的时候没有accept成功。一般服务器拒绝连接的情况有两种,一种情况是信息不符合要求而拒绝连接,还有一种情况是连接池的数量已经超出连接池的最大数量。结合第一点信息,我们知道netty的并发能量很强,不会在少量并发的情况下拒绝外部连接。基于以上信息我们得出一个可能的结论:网关存在内存泄漏!
,导致句柄耗光后无法接收新的连接。
既然可能是内存泄露,那么我们要想办法定位到内存泄漏的地方。要么是自己编码不正确造成的内存泄漏,要么是框架不小心自己造成的内存泄漏。
到此我们差不多已经有了一个大概的方向,不过不急,我们乘胜追击,接着分析第三点。
第三点的信息也很有意思,NativeIoException 是一个Native打头的异常,这种异常并不常见。按照Java的编码命名习惯,Native意味着native层,也就是NativeIoException 这个异常发生在C/C++层。熟悉Java的人都知道,C/C++是系统的底层,而我们传统意义上使用Java框架的时候不会涉及到Native的编码,因此我们可以断定,内存泄漏的位置在Native层,也就是说这是一个框架的bug!
因为我们可以得出结论,这是一个发生在native层的框架bug!
三、解决办法
定位到问题所在后,那么解决的办法就简单啦。
我们拿这个issue上gateway的GitHub仓库上搜索,果然找到一个跟'Too many open files'相关的issue,而且该issue已经是Closed状态。说明已经有网友碰到过类似的问题而且提交了issue,而Closed状态意味着得到修复。
我们将spring-boot和spring-cloud同样做了升级,直接升级到比较稳定的版本。即升级SpringBoot版本到2.3.4.RELEASE,顺便把SpringCloud的版本升级到Hoxton.SR8。
升级后发布到线上,该异常没有再出现过。
四、题外话
关于这个bug的说明,在GitHub的issue上有充分的讨论。据issue上的讨论,最终的锅指向netty框架,这一点和上面我们分析得出的结论一致。
而springCloudVersion=Hoxton.SR6
和 springBootVersion=2.3.1.RELEASE
正好引用了有问题的netty版本,据issue上讨论,有问题的netty版本是0.9.8版本。
具体的讨论在以下地址的网页上,有兴趣的小伙伴可以去观摩下大神们的讨论。