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文件,发现有如下信息:


 

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章