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 |