2018年4月8日星期日

OOM的触发条件与配置

    OOM(Out of Memory)指Linux在无可用内存时的一个最后策略。当系统中可用内存过少时,OOM killer会选择kill某些进程,来释放这些进程所占用的内存。

1.何时触发oom killer

现代内核通常在分配内存时,允许申请的内存量超过实际可分配的free内存,这种技术称为Overcommit,可以认为是一种内存超卖的策略。开启了Overcommit,其实是基于一个普遍的规律——大部分应用并不会将其申请的内存全部用满——来尽量提升内存的利用率,可以允许系统分配出的内存总和超过系统能提供之和(物理内存+Swap空间),但是这种做法也导致OOM的风险。

我们来看看overcommit的策略。

通过内核参数vm.overcommit_memory来进行控制,取值如下
  • 0 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
    • 启发式策略,后果比较严重的Overcommit将不能成功,而轻微的Overcommit将被允许。(如何来区分这两种情况?)
  • 1 永远允许Overcommit,这种策略适合那些不能承受内存分配失败的应用,比如某些科学计算应用。
  • 2 永远禁止Overcommit,在这个情况下,系统所能分配的内存不会超过swap+RAM*系数(/proc/sys/vm/overcmmit_ratio,默认50%,你可以调整),如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序。
 注意: overcommit_memory=2就意味着关闭了oom killer!生产环境下并不推荐:如果遇到内存完全无法分配的情况而又无法通过oom来释放进程占用的内存,可能会触发不可预期的行为,比如内核panic,服务器hang死等。

内存overcommit量
  • vm.overcommit_ratio 是指定了一个比例,而vm.overcommit_kbytes是指定一个绝对大小值。通过/proc/meminfo中的Commit值可以看到当前生效的比例和值。
#grep -i commit /proc/meminfo
CommitLimit:    101037524 kB    //内存分配上限,CommitLimit = 物理内存 * overcommit_ratio(默认50,即50%) + swap大小
Committed_AS:   152507000 kB //是已经分配的内存大小                                           
所以,当vm.overcommit_memory为0或者1时,实际就是允许OOM killer工作。当内存没有swap可用,又无法reclaim到空闲内存时,就会触发oom killer选进程进行kill了。

2.kill谁?

      OOM killer的目标是选出最佳进程(一个或者多个)去kill,进程的考量需要期望兼顾一次kill释放出最多的内存,同时也要对系统重要性最小。
     为实现这个目标,内核对每个进程维护了一个评分oom_score,可以通过/proc/<PID>/oom_score看到。分数越高则被kill的可能性越大。
调整oom kill相关的入口(详细介绍见man proc)

/proc/<pid>/oom_adj
    调小可以降低被OOM killer选中的概率。从 -16 to +15来调整被选中的概率,如果设置为-17(OOM_DISABLE)的话,可以彻底避免进程被oom killer。默认值是0.
    注意: oom_adj在Linux 2.6.36之后就废弃了,改用oom_score_adj,不过为向前兼容,改oom_adj或者oom_score_adj任意一个,另一个会做等比例的调整。

/proc/<pid>/oom_score
    这个可以用来标记多大可能性进程会被oom killer选中。0表示不会被kill。值越大表示被选中kill概率越高。oom_score值的基础考量是进程用到的内存,结合下面一些考量点适当加+或者减-:
【+】是否使用fork创建了大量子进程
【-】是否长时间运行,或使用了大量CPU时间
【+】是否有更低nice优先级(比如>0,越大优先级越低)
【-】进程是否是privileged
【-】进程是否在直接访问硬件
再加上对oom_score_ad以及oom_adj调整的综合计算,得出最后打分值。

/proc/<pid>/oom_score_adj
    取值范围-1000到1000。要说明这个值首先得明白oom killer选择对象时的启发式的打分策略(badness heuristic)。
    这个策略会对所有任务打分0-1000,0表示永远不kill,而1000表示总是kill。这个值一定程度上反应的是任务使用了可用内存的比例(比如任务所有可用内存为100G,使用了100G,则打分1000.使用50G则打分500)。注意这里进程的“可用内存”指的是OOM-killer运行时系统内允许该进程使用的内存,跟CPUSET所占内存或者设置的内存上限有关。
    root用户进程通常被认为比较重要,比其他进程高3%的优惠(adj -= 30; 分数越低越不容易被杀掉)。
    oom_score_adj在系统打分基础上进行调整,进一步控制进程是优先还是尽量避免被kill。
    更详细的打分算法见https://github.com/torvalds/linux/blob/master/mm/oom_kill.c

所以,oom_score最高的就是会被OOM killer优先处理的进程,要控制进程的优先级,调整oom_score_adj。

3.OOM的其他控制

1)配置OOM后直接panic
我们也可以配置,当触发了OOM后,直接整个服务器重启。
sysctl -w vm.panic_on_oom=1
sysctl -w kernel.panic=10  //10秒后自动重启系统
echo "vm.panic_on_oom=1" >> /etc/sysctl.conf
echo "kernel.panic=10" >> /etc/sysctl.conf

2)配置手动触发一次OOM killer
echo f> /proc/sysrq-trigger
手动启用oom-kill机制,会自动杀掉oom_score指数评分最高的那个进程。

4.诊断OOM

1)如何识别出是否发生了OOM
OOM killer会将kill的信息记录到系统日志/var/log/messages,检索相关信息就能匹配到是否进行了
grep 'Out of memory' /var/log/messages

也可以通过dmesg
dmesg | egrep -i 'killed process'
这个命令查看的 oom 的时间里是时间戳的形式,如果你的 dmesg 没有-T这个时间的选项,那么就需要通过
date -d "1970-01-01 UTC `echo "$(date +%s)-$(cat /proc/uptime|cut -f 1 -d' ')+12288812.926194"|bc ` seconds"
来转换成可读的时间了.

2)找出最有可能被OOM killer杀掉的进程
从上文就可以看出,检查每个进程的oom_score,找出最高的即可。
cat > 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

参考:
https://serverfault.com/questions/762017/how-to-get-the-linux-oom-killer-to-not-kill-my-process
http://www.oracle.com/technetwork/articles/servers-storage-dev/oom-killer-1911807.html
https://unix.stackexchange.com/questions/153585/how-does-the-oom-killer-decide-which-process-to-kill-first
https://lwn.net/Articles/668126/#reaper/
https://www.vpsee.com/2013/10/how-to-configure-the-linux-oom-killer/
https://lwn.net/Articles/317814/


没有评论:

发表评论