概述
最近测试提了个bug
,说批量图片上传会返回500
错误。然后我弄了两个zip
压缩包一个3MB
,一个120MB
,发现3MB
的压缩包每次上传都没问题,而120MB
的每次上传都会报500
,于是上spring cloud gateway
网关查看了下日志,发现网关报了一个莫名奇妙的错误
Connection prematurely closed BEFORE response
意思为连接在响应前过早关闭了。显得莫名奇妙,然后拿着这个报错百度google
了一番,没找到答案,于是换个方向。小文件没有问题,而大文件出问题,那么会不会是spring cloud gateway
不支持大文件上传?然后到官方找对应issue
,果然找到了一个类似的issue
是17年的。dose gateway support large file transfer
,然而并未找到答案。又去问了下测试,以前可不可以,测试说,以前是正常的。就是这段时间不行的。原因是这个接口改动了。然后本地起项目调试了下,发现一切正常。然后又测试了下测试环境的,发现每次返回这个错误的时候,相关服务就宕机重启了。好吧,去看下pod的状态
kubectl -n test describe po/xxxx
查看Last Error
为OOMKiller
,莫名奇妙,文件上传怎么会oom呢?然后去看了下相关配置
k8s 的配置
resources:
limits:
cpu: 2
memory: "4Gi"
requests:
cpu: 2
memory: "4Gi"
jvm配置
"-Xms4096m -Xmx4096m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m
我的天,开发在部署项目的时候啥都不看的,这样配置肯定导致native memory不足啊。接着又去看了下代码。
所以里面调整下参数,预留500MB给native重启下,在测试下,果断就好了。
所以部署项目的时候还是要稍微注意下此类问题。不然排查问题又要老半天。
那么这里引出一个问题。不管有没有以上问题,这个堆外内存预留本身就是必要的。
为什么要预留的原因
- 操作系统本身需要一定的内存空间运行
- java的应用除了metaspace本身占用了堆外内存外,还有其它的方式可能占用堆外内存,比如nio,jni等。
关于堆外内存
- 堆外内存的最大大小可以通过 -XX:MaxDirectMemorySize 设置
- Java VisualVM中可以安装Buffer Pools插件监控堆外内存
所以规划好内存是一件极其重要的事情。比如给docker分配了多少内存,给堆多少,给元空间多少,应用有没有使用直接内存的情况,该预留多少。
而此次oom的原因就是没有堆内存做规划,图片批量上传,由于文件比较大,使用到了nio的堆外内存.
关于NIO
在NIO中使用FileChannel作为读写文件的通道,并且读写使用了缓冲区。而这个缓冲区有两种实现,一种是使用堆内存HeapByteBuffer,第二种是使用堆外内存DirectByteBuffer,使用堆外内存只需要在堆上分配一个指针指向堆外内存即可
使用堆外内存单从性能上来说的优势是少了一层操作系统到jvm虚拟机间的内存拷贝,使得性能得到了一定层度上的提升。从垃圾回收上来说,减少了GC的负担。
而本次的批量图片上传接口就使用到了nio的堆外内存,加上配置问题,导致了OOM,代码如下
public static void copy(FileInputStream fis, FileOutputStream fos) {
FileChannel in = null;
FileChannel out = null;
try {
in = fis.getChannel();
out = fos.getChannel();
int maxCount = (64 * 1024 * 1024) - (32 * 1024);
long size = in.size();
long position = 0;
while (position < size) {
position += in.transferTo(position, maxCount, out);
}
} catch (Exception e) {
throw ExceptionUtils.wrap2Runtime(e);
} finally {
if(in != null) {try{in.close();}catch(Exception e){}}
if(out != null) {try{out.close();}catch(Exception e){}}
if(fis != null) {try{fis.close();}catch(Exception e){}}
if(fos != null) {try{fos.close();}catch(Exception e){}}
}
}
//该方法需要三个参数一个是当前位置,一个是传输的最大大小和需要输出到哪里去
public long transferTo(long position, long maxCount, WritableByteChannel out) throws IOException {
this.ensureOpen();
if (!var5.isOpen()) {
throw new ClosedChannelException();
} else if (!this.readable) {
throw new NonReadableChannelException();
} else if (var5 instanceof FileChannelImpl && !((FileChannelImpl)var5).writable) {
throw new NonWritableChannelException();
//判断当前位置是否大于0,传输的最大大小是否大于0
} else if (position>= 0L && maxCount>= 0L) {
long var6 = this.size();//获取输入流大小
if (position> var6) {
return 0L;
} else {
//将缓冲区大小和Ineger比较取小,也就是一次传输的大小最大不能超过2GB
int var8 = (int)Math.min(maxCount, 2147483647L);
//判断文件大小-已传输的大小是否小于需传输的大小,如果小于则让需要传入的大小变更为当前大小。
if (var6 - position< (long)var8) {
var8 = (int)(var6 - position);
}
long var9;
if ((var9 = this.transferToDirectly(position, var8, var5)) >= 0L) {
return var9;
} else {
return (var9 = this.transferToTrustedChannel(position, (long)var8, var5)) >= 0L ? var9 : this.transferToArbitraryChannel(position, var8, var5);
}
}
} else {
throw new IllegalArgumentException();
}
}
最后
以上就是快乐大侠为你收集整理的在k8s下坑人的OOM问题的全部内容,希望文章能够帮你解决在k8s下坑人的OOM问题所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复