Mesos Task Killed by OOM Killer
2015-11-15 13:49
796 查看
问题
前几天工作中遇到一件很“诡异”的事,我用Mesos启动的task总是运行了一段时间就自己死掉了(FAILED),查看log发现进程的退出码是137,即被人强行kill了(SIGKILL)。通常会做这种事的就是OOM killer了。查看了dmesg之后发现果然是由于内存用量超出了设置的限定量而被OOM killer杀掉了。但是“诡异”的就在于,我仔细地观察了该task运行的容器里面的所有进程的内存用量(即top命令输出中的RES列),加起来比我设置的限制量要小得多得多,为什么OOM killer认为它超出限制量了呢?原因
在google了一轮之后,终于在cgroups的维基百科页上找到了一个可疑点:cgroups的内存限制包括file system cache!File system cache,即page cache,可以暂且简单地认为是读写硬盘的一个buffer。由于机械硬盘的寻址比较慢,所以每次读写的overhead很大,所以用内存来作为读写硬盘的一个buffer可以提高整体的读写速度。Linux就是利用空闲的内存来做page cache的。
这样看来就有合理的解释了。我的task要从网络上下载文件,最快的时候要以每秒50MB的速度下载10G以上的文件。如此快的下载速度,而我的硬盘速度又比较慢,所以kernel来不及清理page cache使得page cache的用量迅速上升,很快就过了我设置的内存限制量,所以OOM killer就开始杀掉我的进程了。为了确认这一点,我又仔细地看了一眼dmesg中关于OOM的部分,原来有列出来各类内存的使用量,果然其中cache的使用量占了大部分。
解决方法
Linux kernel有与page cache相关的参数,如下:sysctl -a | grep dirty
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 500
这些参数可以很好地限制和调整page cache的使用。比如vm.dirty_bytes,就是说当尚未写入硬盘的page cache达到多少的时候kernel会开始阻断所有I/O操作直到这些page cache写入硬盘。尽管阻断所有I/O操作可能会导致系统有较明显的卡顿,但是这的确有效避免了内存使用量超出限制额的情况。可惜的是,这些参数都是kernel层的,而同一个Mesos slave上面运行的所有task都是share同一个kernel的,所以如果我在我的容器里更改了其中的一些参数,那么在同一个slave上面运行的其他task也会受到影响,这不是我想要的,所以这个方法不行。
另外一个办法就是在每下载了文件的一部分之后就去主动让kernel清理page cache,比如用这个command:sync && echo 3 > /proc/sys/vm/drop_caches
但是结果还是被OOM杀掉,原因是kernel只会清理已经写入硬盘的缓存,还未写入硬盘的缓存(即dirty page)是不能清理的,否则这部分数据就完全丢失啦!所以如果硬盘的写入速度就是不够快,那这种方法根本无济于事。
所以这样看来唯一的办法就是限制下载速度了。我的task那部分是用Java写的,想要限制下载速度,马上让我想到的有两种方法:
1)用ProcessBuilder类来启动一个wget或curl进程,也就是用wget或者curl下载,这两个软件都有参数可以限制下载速度。例如:
void downloadWithLimitedRate(String downloadUrl, String rateLimit) {
ProcessBuilder wgetProcessBuilder = new ProcessBuilder("wget","--limit-rate="+ rateLimit, downloadUrl);
try {
Process wgetProcess = wgetProcessBuilder.start();
wgetProcess.waitFor();
} catch(IOException | InterruptedException ex) {
//handle the exceptions according to your needs
}
}
2)如果用纯Java的话,可以用FileChannel,每次下载一定数量的字节,然后sleep一会,再继续。比如:
void downloadFile(URL downloadUrl, String fileSavePath) {
try {
URLConnection conn = downloadUrl.openConnection();
try(InputStream is = conn.getInputStream();
ReadableByteChannel rbc = Channels.newChannel(is);
FileOutputStream fos = new FileOutputStream(new File(fileSavePath))) {
long currentPosition = 0;
while(true) {
long numBytesTransferred = fos.getChannel().transferFrom(rbc, currentPosition, BYTES_TRANSFER_PER_ROUND);
if(numBytesTransferred == 0) {
break;
}
currentPosition += numBytesTransferred;
try {
Thread.sleep(SLEEP_PER_ROUND_IN_MS);
} catch(InterruptedException ex) {
}
}
}
} catch(IOException ex) {
//handle exceptions according to your needs
}
}
相关文章推荐
- Enterprise Library for .NET Framework 2.0缓存使用实例
- PowerShell中编程清空IE缓存方法
- PowerShell中使用.NET将程序集加入全局程序集缓存
- C#中缓存的基本用法总结
- wap开发中如何有效的利用缓存减少消息的传送量
- PHP基于文件存储实现缓存的方法
- smarty缓存用法分析
- 引用全局程序集缓存内的程序集的方法
- asp Response.flush 实时显示进度
- C#实现清除IE浏览器缓存的方法
- ASP.NET缓存管理的几种方法
- PHP文件缓存类实现代码
- 清除aspx页面缓存的程序实现方法
- C#缓存之SqlCacheDependency用法实例总结
- jQuery数据缓存用法分析
- Jquery validation remote 验证的缓存问题解决方法
- IE9下Ajax无法刷新数据的缓存问题解决方法
- Ajax获取页面被缓存的解决方法
- ASP.NET网站管理系统退出 清除浏览器缓存,Session的代码
- IE cache缓存 所带来的问题收藏