OOM 简介
Out Of Memory Killer 是 Linux 的一种系统保护机制,实现了内存紧张时 kill 掉某些进程防止系统卡死的问题。内核官方文档在此 kernel vm instruction。
Kill 的默认机制是扫描所有进程任务的内存占用、CPU占用等因素然后打分(badness),分值越高,kill 的优先级越高。进程分值可以在 /proc/PID/oom_score 文件中查看。分值范围为-17 ~ 50。可以通过手动将一个进程的 oom_score 配置为-17来防止该进程被 kill。
OOM Killer 配置有两种方法:
- 在 /etc/sysctl.conf 中配置,然后 sysctl -p 更新
- 直接 echo 值到 /proc/sys/vm 中对应的参数接口
OOM 常用配置项
vm.panic_on_oom:触发 oom 机制时是否触发 kernel panic,打开会在触发OOM时重启机器,推荐配置为 0(关闭)
vm.oom_kill_allocating_task:直接 kill 掉触发 oom 机制的进程,而不去扫描进程然后打分(会占用比较多的资源)。此案例中推荐配置为 1 打开,因为内存泄露的进程会以很快的速度占满内存,很可能再扫描打分结束前系统就 freeze 了。
vm.overcommit_memory:是否允许程序申请过量的内存,默认为0。有0,1,2三个选项(此案例推荐为2,平时推荐为0):
0:内核会预估是否有充足的内存,然后再为进程分配内存
1:内核会永远认为有充足的内存可用,进程申请内存时总是允许
2:内核永远不允许进程申请定额以上的内存,定额有两个参数可以配置
vm.overcommit_kbytes:最大允许申请的内存,单位为 kbytes,配置后,应用程序不允许申请 swap + 该值 以上的内存。默认为0表示禁用。
Redis 引发系统OOM Killer
昨晚(2016-9-5),生产环境的Redis发生警报,一段时间后,内存被降到50%多(之前一直在90%左右),然后发现Redis的进程挂了。第一时间看Redis的log文件,发现有如下信息:
1 2 3 4 5 6 7 8 9 | 10866:M 05 Sep 20:15:19.711 # Background saving terminated by signal 9 10866:M 05 Sep 20:18:43.898 # Background saving terminated by signal 9 10866:M 05 Sep 20:26:46.434 # Background saving terminated by signal 9 10866:M 05 Sep 20:34:49.161 # Background saving terminated by signal 9 10866:M 05 Sep 20:42:52.406 # Background saving terminated by signal 9 10866:M 05 Sep 20:42:55.332 # Background saving terminated by signal 9 1758:M 05 Sep 21:28:11.114 # Background saving terminated by signal 9 1758:M 05 Sep 21:30:18.479 # Background saving terminated by signal 9 1758:M 05 Sep 21:32:55.275 # Background saving terminated by signal 9 |
可知Redis收到kill -9
的信号终止了.然后,当时第一反应,应该是“有人”人工去kill Redis进程吗?不知道怎么的,当时自己就去查看操作系统日志dmesg -T | grep redis
,真的发现是有内幕:
1 2 3 4 5 6 7 8 9 10 11 12 | dmesg -T | grep redis | grep "Out of memory" [Mon Sep 5 20:15:18 2016] Out of memory: Kill process 725 (redis-server) score 517 or sacrifice child [Mon Sep 5 20:18:42 2016] Out of memory: Kill process 786 (redis-server) score 517 or sacrifice child [Mon Sep 5 20:26:45 2016] Out of memory: Kill process 914 (redis-server) score 517 or sacrifice child [Mon Sep 5 20:34:48 2016] Out of memory: Kill process 1022 (redis-server) score 517 or sacrifice child [Mon Sep 5 20:42:50 2016] Out of memory: Kill process 1127 (redis-server) score 517 or sacrifice child [Mon Sep 5 20:42:52 2016] Out of memory: Kill process 10866 (redis-server) score 517 or sacrifice child [Mon Sep 5 20:50:57 2016] Out of memory: Kill process 1235 (redis-server) score 517 or sacrifice child [Mon Sep 5 20:50:57 2016] Out of memory: Kill process 10866 (redis-server) score 517 or sacrifice child [Mon Sep 5 21:28:10 2016] Out of memory: Kill process 1886 (redis-server) score 479 or sacrifice child [Mon Sep 5 21:30:17 2016] Out of memory: Kill process 1758 (redis-server) score 479 or sacrifice child [Mon Sep 5 21:32:54 2016] Out of memory: Kill process 1972 (redis-server) score 479 or sacrifice child |
1 2 3 | dmesg -T | grep redis | grep "oom-killer" [Mon Sep 5 20:26:44 2016] redis-server invoked oom-killer: gfp_mask=0x10200da, order=0, oom_score_adj=0 [Mon Sep 5 21:32:53 2016] redis-server invoked oom-killer: gfp_mask=0x8200da, order=0, oom_score_adj=0 |
当天查看的Redis内存信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # Memory used_memory:7877146344 used_memory_human:7.34G used_memory_rss:8699490304 used_memory_rss_human:8.10G used_memory_peak:8462552976 used_memory_peak_human:7.88G total_system_memory:16828653568 total_system_memory_human:15.67G used_memory_lua:37888 used_memory_lua_human:37.00K maxmemory:9573741824 maxmemory_human:8.92G maxmemory_policy:noeviction mem_fragmentation_ratio:1.10 mem_allocator:jemalloc-4.0.3 |
原因
当时服务器还有个MySQL slave在进行复制备份,服务器一共16GB的内存,然后MySQL用掉了5GB,还有11GB内存,除去一些其他的简单的占用和消耗外,估计还有10GB的内存真正给Redis可用。
可以看到上面的INFO,当时分配给Redis最大的内存为差不多9GB,那应试还有1GB可用空间,那到底是什么导致了OS触发OOM Killer
机制呢?
原来,Redis当时开启了RDB功能,而Redis自身是通过fork()
进程来处理RDB文件的。可以man fork
知道,它是精确复制与父进程来处理RDB文件的。
Redis在后台的存储机制依赖于操作系统中fork的copy-on-write:也就是redis fork(创建一个子进程)是父进程的一个完整精确拷贝。子进程转储到磁盘上的数据库然后退出。理论上来说,子进程作为一个副本应该使用和父亲一样多的内存,但是实际上由于大部分现代操作系统的copy-on-write的实现,父进程和子进程将共享内存页。当他被父进程或者子进程改变的时候,一个内存页将被复制。因此,从理论上讲,当子进程存储的时候,所有内存页可能被改变,Linux不能提前告诉子进程多少内存被使用,所以如果overcommit_memory设置被设置为0,创建将会失败,除非有同样多的空闲内存。结果是,如果你有3GB的redis数据并且只有2GB的空闲内存,它将会失败。
把overcommit_memory设置为1来告诉Linux以更加乐观的方式来执行fork操作,并且这确实是你想要的。
由于REdis一般占大内存,所以通常需要关闭系统的OOM,方法为将“/proc/sys/vm/overcommit_memory”的值设置为1(通常不建议设置为2),也可以使用命令sysctl设置,如:sysctl vm.overcommit_memory=1,但注意一定要同时修改文件/etc/sysctl.conf,以便得系统重启后仍然生效:
# vi /etc/sysctl.conf vm.overcommit_memory=1 |