Cannot allocate memory /Resource temporarily unavailable 故障分析
前言:
Linux 核心有個機制叫OOM killer(Out-Of-Memory killer),該機制會監控那些佔用記憶體過大,尤其是瞬間很快消耗大量記憶體的程序,為了防止記憶體耗盡而核心會把該程序殺掉。典型的情況是:某天一臺機器突然ssh遠端登入不了,但能ping通,說明不是網路的故障,原因是sshd程序被OOM killer殺掉了(多次遇到這樣的假死狀況)。重啟機器後檢視系統日誌/var/log/messages會發現Out of Memory: Kill process 1865(sshd)類似的錯誤資訊。
客戶工單詳情如下:
首先思考限制一個程序的建立條件涉及哪些方面資源的問題
程序無法建立了,可能原因,pid耗盡、open files等資源耗盡、記憶體耗盡、有充足的記憶體,但還是會觸發OOM(是因為該程序可能佔用了特殊的記憶體地址空間)
下面逐一展開分析
一、pid耗盡類
1、問題分析
檢視當前程序數:ps -eLf | wc -l
檢視pid_max數目:# sysctl kernel.pid_max
通過上述數值比較可確定是否是pid耗盡導致程序fork失敗
2、解決方案
修改數目:sysctl -w kernel.pid_max=65535(適加較上次增加,64位系統上pid_max最大值為2^22,32位系統上最大值為32768)
#echo 1000000 > /proc/sys/kernel/pid_max 臨時生效
#echo "kernel.pid_max=1000000 " >> /etc/sysctl.conf
#sysctl -p 永久生效
3、故障復現
a、查詢pid_max值並修改(原來值挺大,為了測試改小點,模擬pid資源不足)
# sysctl kernel.pid_max
kernel.pid_max = 32768
# sysctl -w kernel.pid_max=500
kernel.pid_max = 500
b、建立自動fork程序指令碼
f
c、編譯後執行
# gcc test.c; #./a.out
再用其他終端試圖ssh該測試環境,發現無法登入。
d、 在其他已經連線該測試環境的終端(執行a.out之前就連線好)試圖執行free或者其他命令,結果報錯
e、增大pid數目:
# sysctl -w kernel.pid_max=32768,count is 2046,明顯看到count數目提升
二、記憶體耗盡類
1、問題分析
記憶體耗盡主要是因為核心系統程序、使用者業務程式在大量佔用記憶體、或者是記憶體洩漏
程序fork失敗,在小記憶體的程序上做一個fork,不需要太多資源,但當這個程序的記憶體空間以G為單位時,fork就成為一件很恐怖的操作。比如在16G記憶體的主機上fork 14G記憶體的程序呢?肯定會報記憶體無法分配的。
首先可以通過free、top命令判斷是否是記憶體資源不足,再 通過meminfo檢視記憶體的使用情況以及通過cat /proc/slabinfo 檢視具體的mem使用情況。以上三個方法可以初步的排查,是否是因為核心或者使用者程式在佔用記憶體,達到初步定為問題的目的
對於殭屍程序導致的記憶體不足排查,也可以通過檢視message裡面的日誌資訊 :cat /var/log/messages|grep -i error。下圖是systemd程序發生bug,導致不再wait子程序,不再回收結束程序資訊,導致出現大量殭屍程序;使用者crontab中,凡是末尾加了“&”,即,以後臺程序執行的任務,其父程序都將變成systemd(1號程序),進而導致這些程序全部變成殭屍程序。
2、解決方案
a、 增大記憶體條(條件允許,優先選擇)或通過手動釋放記憶體快取的形式來進行釋放。
To free pagecache:
- echo 1 > /proc/sys/vm/drop_caches
To free dentries and inodes:
- echo 2 > /proc/sys/vm/drop_caches
To free pagecache, dentries and inodes:
- echo 3 > /proc/sys/vm/drop_caches
b、記憶體不足並且沒有啟用交換空間,要啟用交換,可以使用:
/bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024
/sbin/mkswap /var/swap.1
/sbin/swapon /var/swap.1
c、對於殭屍程序問題
殭屍程序增多,無法kill -9,在系統宕機的情況下只能重啟恢復,升級systemd。如果可以在不重新啟動伺服器的情況下,停止佔用系統資源的程序,則應檢查所有必需的系統守護程式是否仍在執行。如果無法正常登入以檢視正在進行的操作,請嘗試從系統控制檯以root使用者身份登入。當系統資源不足時,控制檯比常規登入略有優勢。
d、保護重要程序不被核心殺掉
運維過程中保護的一般是sshd和一些管理agent。保護某程序不被核心殺掉可以這樣操作:
# echo -17 > /proc/$PID/oom_adj
比如:防止sshd被殺,可以這樣操作:
# pgrep -f "/usr/sbin/sshd" | while read PID;do echo -17 > /proc/$PID/oom_adj;done
可以在計劃任務里加入這樣一條定時任務,就更安全了:
#/etc/cron.d/oom_disable
*/1**** root pgrep -f "/usr/sbin/sshd" | while read PID;do echo -17 > /proc/$PID/oom_adj;done
為了避免重啟失效,可以寫入/etc/rc.d/rc.local
echo -17 > /proc/$(pidof sshd)/oom_adj
【還可以通過修改核心引數禁止OOM機制# sysctl -w vm.panic_on_oom=1 vm.panic_on_oom = 1 //1表示關閉,預設為0表示開啟OOM # sysctl -p】(非必須情況下不建議修改)
補充:通過echo 1 >/proc/sys/vm/overcommit_memory,修改vm.overcommit_memory值
(修改vm.overcommit_memory引數方法與上述d方法不同,它會產生副作用就是此時cvm上的服務已經不能正常運行了,方法只是保證系統不hang住,而d方法的程序是可執行)
Linux核心會根據引數vm.overcommit_memory引數的設定決定是否放行(預設為0)
vm.overcommit_memory = 1,直接放行。echo 1 >/proc/sys/vm/overcommit_memory
vm.overcommit_memory = 0:則比較 此次請求分配的虛擬記憶體大小和系統當前空閒的實體記憶體加上swap,決定是否放行。
vm.overcommit_memory = 2:則會比較 程序所有已分配的虛擬記憶體加上此次請求分配的虛擬記憶體和系統當前的空閒實體記憶體加上swap,決定是否放行。
3、故障復現
a、在沒有記憶體,沒有pid限制的情況下編譯後執行# gcc test.c; ./a.out,結果如圖
b、使用壓力測試-memtester工具打壓
下載
wget http://pyropus.ca/software/memtester/old-versions/memtester-4.2.2.tar.gz
安裝
tar zxvf memtester-4.2.2.tar.gz
cd memtester-4.2.2
make && make install</span>
# nohup memtester 1500M > /tmp/memtest.log &
再行./a.out,結果如圖
三、最大檔案開啟數限制
1、問題分析
意思是說程式開啟的檔案數過多,不過這裡的files不單是檔案的意思,也包括開啟的通訊連結(比如socket),正在監聽的埠等等,所以有時候也可以叫做控制代碼(handle),這個錯誤通常也可以叫做控制代碼數超出系統限制。 引起的原因就是程序在某個時刻打開了超過系統限制的檔案數量以及通訊連結數,通過命令ulimit -a可以檢視當前系統設定的最大控制代碼數
2、解決方案
a、命令列輸入:#ulimit -n 2048 (臨時生效)或通過vi /etc/profile修改,在最後行加入:ulimit -n 32768
b、vim /etc/security/limits.conf (永久修改)
#在最後加入
* - nofile 8192 ( * 表示所有使用者,可根據需要設定某一使用者)
3、故障復現
初始狀態
執行#./a.out後中端斷聯,重新開一個終端也登陸不進去
復現現象成功!
補充方法:有時可能是由於伺服器本身或特定於您的使用者帳戶的某些資源限制。可以通過#ulimit -a檢查shell中的限制
#ulimit -u檢查最大使用者程序
四、總結
對於記憶體洩漏問題,多是由於程式開發時的程式碼邏輯不好,這裡不討論,還有有時使用者業務程式導致業務程序數增多,通過增大程序數解決問題。但是由於在程序數達到很高時,記憶體已經幾乎耗盡,所以增大程序數只是緩解。建議使用者優化業務程式碼!