问题描述
线上运行一段时间的gateway服务网关会莫名其妙的“崩掉”,请求不通,看日志每个请求都报错,报错的大致意思是分配内存的时候,超过项目设置的最大内存。
这属于是比较典的内存溢出问题,只要找到没有释放内存的问题代码即可。
排查步骤
- 首先看看线上运行的gateway内存情况,看看内存占用是不是一直在涨
但开了几天的监控页面,发现内存占用一直很低,甚至都还没触发堆内存的扩容(设置最大内存1G,但一直保持在500MB),非常诡异。。。。
大概曲线就和下面图一样,比较平稳
- 那就怀疑是某个请求带了超大数据,让gateway内存崩掉
但项目里面数据最大的请求也不超过5M,并且用postman并发访问,gateway的堆内存都会被很好的回收,并不会造成内存溢出。。。
- 又将跑了几天的gateway的heapdump文件下载下来研究一下
发现也没有问题,占用内存加起来不超过200M。。。。。
- 线上的环境排查没招了,只有本地跑一个gateway看能不能复现,直接在本地设置服务最大内存50MB,然后用postman调用接口猛测
调用了一个数据量巨大的接口,返回数据5M左右,但连续猛调了几十次,gateway也没崩,看监控堆内存也被回收了的,说明普通的接口访问大概率不会造成溢出
- 接着怀疑是文件上传导致的
马上用gateway上传24MB的PPT文件,第一次成功,第二次果然触发了同款报错,之后所有的请求过来,都会提示“内存分配超限制”,说明上传文件的请求会导致内存无法回收
但实际上项目中的文件上传功能用的很少,一个月上传的文件可能都不超过100MB,所以怀疑导致问题的请求可能跟文件上传请求一样有共同的逻辑
- 接下来就是研究文件上传代码跟普通代码的逻辑区别了
gateway的主要逻辑都在几个filter里面,经过排查,在参数加密解密的filter里面需要参数解密的请求会调用这样一个方法resolveBodyFromRequest(),里面有一段代码DataBufferUtils.release(buffer);
Flux
是一个响应式流,用于处理异步数据流,这个方法通过DataBufferUtils来获取流中的参数。而在顶级的filter里面,DataBufferUtils会将所有请求的流信息用DataBufferUtils订阅。DataBufferUtils.release(buffer)方法则会清除DataBufferUtils中buffer的数据缓冲区。
这就导致,不需要参数解密的请求,DataBufferUtils.release(buffer)不会执行,静态工具类的缓冲区也不会被回收。。。。
导致原因
因为在处理请求的异步数据流的时候,顶级filter用了DataBufferUtils静态的工具类来接收数据(他会为数据创建一个缓冲区,并不会直接占用数据大小的内存)
但释放数据缓冲区的代码写在了参数解密filter里面,且只有post的json和urlecode请求才会走获取参数清理缓冲区的方法,导致不需要数据加密的post请求(如登录请求、文件上传请求、白名单中的请求),就会跳过释放DataBufferUtils缓冲区的方法,久而久之就导致了DataBufferUtils中设置的缓冲区巨大。当某一天缓冲区大小超过服务能给他分配的最大上限时,DataBufferUtils获取数据就会报错之前那个内存分配不足的错。
所以在缓冲区未超过服务极限的情况下,直接观察堆内存占用是发现不了问题的......
解决办法
知道原因之后,解决就很简单了
只需要保证所有请求执行完之后,都清理DataBufferUtils的缓冲区就行了,这个回调在顶级filter的方法中有,在其中写上清理缓冲区的代码就行了