Redis Fork导致OMM killer

Posted wx5bcd2f496a1cf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis Fork导致OMM killer相关的知识,希望对你有一定的参考价值。


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 配置有两种方法:

  1. 在 /etc/sysctl.conf 中配置,然后 sysctl -p 更新
  2. 直接 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文件,发现有如下信息:

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​​,真的发现是有内幕:

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
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内存信息如下:

# 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

 

以上是关于Redis Fork导致OMM killer的主要内容,如果未能解决你的问题,请参考以下文章

Redis架构第五天:Redis其他问题及Redis架构总结

Linux OOM-killer机制(out of memory)

redis在实践中的一些常见问题以及优化思路

理解和配置 Linux 下的 OOM Killer

OOM killer机制

理解和配置 Linux 下的 OOM Killer