您的位置:首页 > 其它

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
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Mesos oom 缓存