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

 

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