1.查看某个容器状态,查看是什么原因退出
[root@docker ~]# docker inspect a6fb3d53a55b | grep -i status -A 10 "Status":
"exited", "Running": false, "Paused": false, "Restarting": false, "OOMKilled":
true, "Dead": false, "Pid": 0, "ExitCode": 137, "Error": "", "StartedAt":
"2021-11-11T00:48:00.806908787Z", "FinishedAt": "2021-11-11T00:48:39.15824301Z"
2.vm.overcommit_memory
Redis在启动时可能会出现这样的日志:
WARNING overcommit_memory is set to 0! Background save may fail under low
memory condition. To fix this issue add 'vm.overcommit_memory = 1' to
/etc/sysctl.conf and then reboot or run the command 'sysctl
vm.overcommit_memory=1' for this to take effect.
在分析这个问题之前, 首先要弄清楚什么是overcommit? Linux操作系统对大部分申请内存的请求都回复yes, 以便能运行更多的程序。
因为申请内存后, 并不会马上使用内存, 这种技术叫做overcommit。 如果Redis在启动时有上面的日志,
说明vm.overcommit_memory=0, Redis提示把它设置为1。
vm.overcommit_memory用来设置内存分配策略, 有三个可选值, 如表:可用内存代表物理内存与swap之和
日志中的Background save代表的是bgsave和bgrewriteaof, 如果当前可用内存不足, 操作系统应该如何处理fork操作。 如果
vm.overcommit_memory=0, 代表如果没有可用内存, 就申请内存失败, 对应到Redis就是执行fork失败, 在Redis的日志会出现:
Cannot allocate memory
Redis建议把这个值设置为1, 是为了让fork操作能够在低内存下也执行成功。
3.oom_badness() 函数
在发生 OOM 的时候,Linux 到底是根据什么标准来选择被杀的进程呢?这就要提到一个在 Linux 内核里有一个 oom_badness()
函数,就是它定义了选择进程的标准。其实这里的判断标准也很简单,函数中涉及两个条件:
* 第一,进程已经使用的物理内存页面数。
* 第二,每个进程的 OOM 校准值 oom_score_adj。在 /proc 文件系统中,每个进程都有一个
/proc/<pid>/oom_score_adj 的接口文件。我们可以在这个文件中输入 -1000 到 1000 之间的任意一个数值,调整进程被 OOM
Kill 的几率。 adj = (long)p->signal->oom_score_adj; points = get_mm_rss(p->mm) +
get_mm_counter(p->mm, MM_SWAPENTS) +mm_pgtables_bytes(p->mm) / PAGE_SIZE; adj
*= totalpages / 1000; points += adj;
结合前面说的两个条件,函数 oom_badness() 里的最终计算方法是这样的:用系统总的可用页面数,去乘以 OOM 校准值
oom_score_adj,再加上进程已经使用的物理页面数,计算出来的值越大,那么这个进程被 OOM Kill 的几率也就越大。
4.Memory Cgroup?
Memory Cgroup 也是 Linux Cgroups 子系统之一,它的作用是对一组进程的 Memory 使用做限制。
第一个参数,叫作 memory.limit_in_bytes。请你注意,这个 memory.limit_in_bytes 是每个控制组里最重要的一个参数了。
这是因为一个控制组里所有进程可使用内存的最大值,就是由这个参数的值来直接限制的。
第二个参数 memory.oom_control 了。这个 memory.oom_control
又是干啥的呢?当控制组中的进程内存使用达到上限值时,这个参数能够决定会不会触发 OOM Killer。
如果没有人为设置的话,memory.oom_control 的缺省值就会触发 OOM Killer。这是一个控制组内的 OOM Killer,和整个系统的
OOM Killer 的功能差不多,差别只是被杀进程的选择范围:控制组内的 OOM Killer 当然只能杀死控制组内的进程,而不能选节点上的其他进程。
第三个参数,也就是 memory.usage_in_bytes。这个参数是只读的,它里面的数值是当前控制组里所有进程实际使用的内存总和。
我们可以查看这个值,然后把它和 memory.limit_in_bytes 里的值做比较,根据接近程度来可以做个预判。这两个值越接近,OOM
的风险越高。通过这个方法,我们就可以得知,当前控制组内使用总的内存量有没有 OOM 的风险了。
控制组
控制组之间也同样是树状的层级结构,在这个结构中,父节点的控制组里memory.limit_in_bytes 值,就可以限制它的子节点中所有进程的内存使用。
我用一个具体例子来说明,比如像下面图里展示的那样,group1 里的 memory.limit_in_bytes 设置的值是 200MB,它的子控制组
group3 里 memory.limit_in_bytes 值是 500MB。那么,我们在 group3 里所有进程使用的内存总值就不能超过
200MB,而不是 500MB。
好了,我们这里介绍了 Memory Cgroup 最基本的概念,简单总结一下:
第一,Memory Cgroup 中每一个控制组可以为一组进程限制内存使用量,一旦所有进程使用内存的总量达到限制值,缺省情况下,就会触发 OOM
Killer。这样一来,控制组里的“某个进程”就会被杀死。
第二,这里杀死“某个进程”的选择标准是,控制组中总的可用页面乘以进程的 oom_score_adj,加上进程已经使用的物理内存页面,
所得值最大的进程,就会被系统选中杀死。