转 http://www.vpsee.com/2013/10/how-to-configure-the-linux-oom-killer/node
最近有位 VPS 客户抱怨 MySQL 平白无故挂掉,还有位客户抱怨 VPS 常常死机,登录到终端看了一下,都是常见的 Out of memory 问题。这一般是由于某时刻应用程序大量请求内存致使系统内存不足形成的,这一般会触发 Linux 内核里的 Out of Memory (OOM) killer,OOM killer 会杀掉某个进程以腾出内存留给系统用,不致于让系统马上崩溃。若是检查相关的日志文件(/var/log/messages)就会看到下面相似的 Out of memory: Kill process 信息:mysql
... Out of memory: Kill process 9682 (mysqld) score 9 or sacrifice child Killed process 9682, UID 27, (mysqld) total-vm:47388kB, anon-rss:3744kB, file-rss:80kB httpd invoked oom-killer: gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0 httpd cpuset=/ mems_allowed=0 Pid: 8911, comm: httpd Not tainted 2.6.32-279.1.1.el6.i686 #1 ... 21556 total pagecache pages 21049 pages in swap cache Swap cache stats: add 12819103, delete 12798054, find 3188096/4634617 Free swap = 0kB Total swap = 524280kB 131071 pages RAM 0 pages HighMem 3673 pages reserved 67960 pages shared 124940 pages non-shared
Linux 内核根据应用程序的要求分配内存,一般来讲应用程序分配了内存可是并无实际所有使用,为了提升性能,这部分没用的内存能够留做它用,这部份内存是属于每一个进程的,内核直接回收利用的话比较麻烦,因此内核采用一种过分分配内存(over-commit memory)的办法来间接利用这部分 “空闲” 的内存,提升总体内存的使用效率。通常来讲这样作没有问题,但当大多数应用程序都消耗完本身的内存的时候麻烦就来了,由于这些应用程序的内存需求加起来超出了物理内存(包括 swap)的容量,内核(OOM killer)必须杀掉一些进程才能腾出空间保障系统正常运行。用银行的例子来说可能更容易懂一些,部分人取钱的时候银行不怕,银行有足够的存款应付,当全国人民(或者绝大多数)都取钱并且每一个人都想把本身钱取完的时候银行的麻烦就来了,银行其实是没有这么多钱给你们取的。linux
内核检测到系统内存不足、挑选并杀掉某个进程的过程能够参考内核源代码 linux/mm/oom_kill.c,当系统内存不足的时候,out_of_memory() 被触发,而后调用 select_bad_process() 选择一个 “bad” 进程杀掉,如何判断和选择一个 “bad” 进程呢,总不能随机选吧?挑选的过程由 oom_badness() 决定,挑选的算法和想法都很简单很朴实:最 bad 的那个进程就是那个最占用内存的进程。git
/** * oom_badness - heuristic function to determine which candidate task to kill * @p: task struct of which task we should calculate * @totalpages: total present RAM allowed for page allocation * * The heuristic for determining which task to kill is made to be as simple and * predictable as possible. The goal is to return the highest value for the * task consuming the most memory to avoid subsequent oom failures. */ unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg, const nodemask_t *nodemask, unsigned long totalpages) { long points; long adj; if (oom_unkillable_task(p, memcg, nodemask)) return 0; p = find_lock_task_mm(p); if (!p) return 0; adj = (long)p->signal->oom_score_adj; if (adj == OOM_SCORE_ADJ_MIN) { task_unlock(p); return 0; } /* * The baseline for the badness score is the proportion of RAM that each * task's rss, pagetable and swap space use. */ points = get_mm_rss(p->mm) + p->mm->nr_ptes + get_mm_counter(p->mm, MM_SWAPENTS); task_unlock(p); /* * Root processes get 3% bonus, just like the __vm_enough_memory() * implementation used by LSMs. */ if (has_capability_noaudit(p, CAP_SYS_ADMIN)) adj -= 30; /* Normalize to oom_score_adj units */ adj *= totalpages / 1000; points += adj; /* * Never return 0 for an eligible task regardless of the root bonus and * oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here). */ return points > 0 ? points : 1; }
上面代码里的注释写的很明白,理解了这个算法咱们就理解了为啥 MySQL 躺着也能中枪了,由于它的体积老是最大(通常来讲它在系统上占用内存最多),因此若是 Out of Memeory (OOM) 的话老是不幸第一个被 kill 掉。解决这个问题最简单的办法就是增长内存,或者想办法优化 MySQL 使其占用更少的内存,除了优化 MySQL 外还能够优化系统(优化 Debian 5,优化 CentOS 5.x),让系统尽量使用少的内存以便应用程序(如 MySQL) 能使用更多的内存,还有一个临时的办法就是调整内核参数,让 MySQL 进程不容易被 OOM killer 发现。github
咱们能够经过一些内核参数来调整 OOM killer 的行为,避免系统在那里不停的杀进程。好比咱们能够在触发 OOM 后马上触发 kernel panic,kernel panic 10秒后自动重启系统。算法
# sysctl -w vm.panic_on_oom=1 vm.panic_on_oom = 1 # sysctl -w kernel.panic=10 kernel.panic = 10 # echo "vm.panic_on_oom=1" >> /etc/sysctl.conf # echo "kernel.panic=10" >> /etc/sysctl.conf
从上面的 oom_kill.c 代码里能够看到 oom_badness() 给每一个进程打分,根据 points 的高低来决定杀哪一个进程,这个 points 能够根据 adj 调节,root 权限的进程一般被认为很重要,不该该被轻易杀掉,因此打分的时候能够获得 3% 的优惠(adj -= 30; 分数越低越不容易被杀掉)。咱们能够在用户空间经过操做每一个进程的 oom_adj 内核参数来决定哪些进程不这么容易被 OOM killer 选中杀掉。好比,若是不想 MySQL 进程被轻易杀掉的话能够找到 MySQL 运行的进程号后,调整 oom_score_adj 为 -15(注意 points 越小越不容易被杀):sql
# ps aux | grep mysqld mysql 2196 1.6 2.1 623800 44876 ? Ssl 09:42 0:00 /usr/sbin/mysqld # cat /proc/2196/oom_score_adj 0 # echo -15 > /proc/2196/oom_score_adj
固然,若是须要的话能够彻底关闭 OOM killer(不推荐用在生产环境):centos
# sysctl -w vm.overcommit_memory=2 # echo "vm.overcommit_memory=2" >> /etc/sysctl.conf
咱们知道了在用户空间能够经过操做每一个进程的 oom_adj 内核参数来调整进程的分数,这个分数也能够经过 oom_score 这个内核参数看到,好比查看进程号为981的 omm_score,这个分数被上面提到的 omm_score_adj 参数调整后(-15),就变成了3:bash
# cat /proc/981/oom_score 18 # echo -15 > /proc/981/oom_score_adj # cat /proc/981/oom_score 3
下面这个 bash 脚本可用来打印当前系统上 oom_score 分数最高(最容易被 OOM Killer 杀掉)的进程:less
# vi oomscore.sh #!/bin/bash for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do printf "%2d %5d %s\n" \ "$(cat $proc/oom_score)" \ "$(basename $proc)" \ "$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)" done 2>/dev/null | sort -nr | head -n 10 # chmod +x oomscore.sh # ./oomscore.sh 18 981 /usr/sbin/mysqld 4 31359 -bash 4 31056 -bash 1 31358 sshd: root@pts/6 1 31244 sshd: vpsee [priv] 1 31159 -bash 1 31158 sudo -i 1 31055 sshd: root@pts/3 1 30912 sshd: vpsee [priv] 1 29547 /usr/sbin/sshd -D