记一次Netty「直接内存溢出」导致线上网关项目宕机排查过程

作为一名Java开发者,我们都知道Java进程是运行在Java虚拟机上的,而Java进程要想正常运行则需要向计算机申请内存,其中主要为Java对象实例所占用的堆(heap)内存(当然还有其他的也会占用内存,比如栈等),这些内存一般划分为Java虚拟机所占内存。

在当今网络通信过程中,不可避免地需要用到高性能IO通信框架Netty,Spring Cloud Gateway也不例外用到了Netty进行网络通信,当然还有很多框架也都应用到了Netty,比如:Dubbo、RocketMQ等等。而Netty为了减少网络通信过程中数据的复制,也就是用户态,内核态之间数据的复制,会大量地分配直接内存,相对于Java虚拟机的堆内存而言,相当于是堆外内存。

而我们本次出现的线上事故也和Netty的直接内存相关。

场景再现

上周四中午,睡得正香,突然线上出现了大量接口502(Http 502错误表示的是网关错误,这个问题是由后端服务器之间不良的IP通讯造成的,可能包括正在尝试访问的网站的 Web 服务器)报警,同时运维监控到我们组刚上线的内网网关发生宕机,情急之下马上先重启了网关服务(万能的重启)重启之后,服务接口可用,不在报警,然后开始排查具体产生宕机的原因,首先跟踪的具体日志如下:

记一次Netty「直接内存溢出」导致线上网关项目宕机排查过程

错误日志

io.netty.util.internal.OutOfDirectMemoryError: failed to allocate

看到以上的日志,大体可以知道是直接内存分配不足导致,为什么会出现分配不足呢,于是有看了最近几天运维监控内存分配情况,如下:

记一次Netty「直接内存溢出」导致线上网关项目宕机排查过程

内存申请分配

其实从上图可以看出,自从服务上线后已用内存就一直在申请、上升,没有释放,那么接下来就是定位为什么会出现内存不释放的问题了,因为我们应用的网关项目是使用的Spring Cloud Gateway进行搭建的,而Spring Cloud Gateway又是使用的Netty框架进行搭建的,这正好和以上报错io.netty.util.internal.OutOfDirectMemoryError日志恰巧对应上,下面就查阅了好多资料,说Gateway低版本确实存在过该问题,升级版本即可解决此类问题,于是将现有的Spring Cloud版本在Finchley基础上升到了Hoxton,并在仿真环境进行了压测(并发1000),压了半个小时,并没有出现宕机异常,于是当天晚上就将代码进行上线,但是上线之后查看ELK日志,发现还是存在很多的错误日志如下:

记一次Netty「直接内存溢出」导致线上网关项目宕机排查过程

内存泄漏日志

LEAK: ByteBuf.release() was not called before it's garbage-collected

竟然是内存泄漏最终导致的内存溢出,按理说像Spring Cloud Gateway这么成熟的框架不应该会出现类似的问题,于是排查我们的项目代码,发现竟然是我们自己网关项目的一个全局过滤XSS攻击的filter,里面有使用Netty的一个databuffer,但是这个databuffer没有进行释放导致,于是将该databuffer进行手工释放DataBufferUtils.release(dataBuffer); 修改完该瑕疵之后,线上内存监控趋于平稳,如下图:

记一次Netty「直接内存溢出」导致线上网关项目宕机排查过程

内存申请使用监控

总结

解决此类内存溢出问题、JVM问题快速的方法一定是结合监控和日志进行排查,因为没有监控和日志我们就无从下手,可能只能考经验和猜,但是这样无疑会浪费大量的时间,所以平时一定要做好监控,以防关键时候手忙脚乱。

还有就是开源的优秀的框架是个好东西,但是我们在使用的过程中一定事先做好评估,也就是可能会遇到问题,带来的弊端,像Netty我们在使用过程中要对内存分配,IO有一定的了解;使用MQ要了解MQ可能会有消息重发、消息顺序、消息丢失等问题;使用Redis作缓存,需要了解如何防止缓存雪崩、缓存穿透等一系列问题。

最后,通过本次线上事故我们也认识到了内存泄漏可能会造成内存溢出的严重问题,内存泄漏不可小觑,使用ThreadLocal时候也得注意。

RocketMQ gateway netty https error dubbo redis Java http ELK JVM 网关 日志 错误 缓存 重启 升级 IT OT
分享到:

您可能还会对下面的文章感兴趣: