題圖:by globe_people from Instagram
上周寫了篇文章,介紹了一個 Java 內存溢出的案例,創下了今年閱讀量最低記錄(不算圖片),為了挽回顏面,我決定再寫一篇,還是從案例入手,介紹 Linux 內存管理機制和一個內存溢出殺手的存在感。
這是以前工作中的一個真實案例:運維人員突然發現有個在線服務失效了。一般遇到這種情況,運維和相關的研發人員都會查看在線的日誌服務,必要的話還會登錄到生產環境的伺服器上去一看究竟。這次略有不同。
通過 ping 可以知道這臺伺服器還「活著」,但是無法通過 ssh 登錄,網絡鏈路確認了沒有問題,在線系統日誌也查不出什麼端倪。排除了所有的可能性之後,我們只能認為,這臺伺服器不僅幹掉了我們部署的服務,而且幹掉了 sshd 進程。
由於這個服務沒有那麼敏感,所以運維人員選擇了直接重啟伺服器(一臺虛擬機),系統啟動後一切都恢復了正常,殺手隱藏在伺服器的 shadow 中,就像一切都沒有發生過。登上伺服器查看系統日誌 /var/log/messages,可以看到類似這樣的信息:
Out of memory: Kill process 862 (xxproid) score 11 or sacrifice child
這條消息表明,某個 pid 是 862 的進程被 OOM Killer 殺掉了,再往下找,還可以找到 sshd 的進程名。在內存極度緊張的時候, sshd 這種不怎麼佔內存的進程也無法倖免。覆巢之下,焉有完卵。
OOM Killer 的全稱叫 Out Of Memory Killer,今天簡單給大家介紹下,它是 linux 內核的內存保護機制。當應用程式大量請求內存導致內存不足的時候,通常會觸發 OOM Killer,OOM Killer 會根據相對簡單粗暴的算法殺掉某個進程以解燃眉之急。如果殺掉一個進程還不足以解決問題,Killer 會按照它對進程的評分,逐次幹掉,分數越高,被幹掉的機率越大,直到內存可以正常使用。
為什麼會出現這種情況呢?這要從 Linux 的內存管理策略說起。
作業系統裡內存管理的主要作用是,進程請求內存的時候為其分配可用內存,進程釋放後回收內存,並監控內存的使用狀況。最簡單的內存管理方式是所有運行的進程對所有內存具備訪問權限,這種方式下,進程必須包含對所需硬體操作的全部代碼,能找到對應的內存地址,然後將自己的數據載入內存。以這種方式進行開發,內存的利用率上不去不說,每個程式設計師差不多都會被內存管理編程搞到吐血,所以這件事得交給作業系統來做。
現代作業系統需要程序能夠共享內存,並且內存的限制對開發者透明,有些程序佔用了內存空間,但不一定是一直使用的,這樣可以把這部分內存數據序列化到磁碟上,需要的時候再加載到內存裡,這樣內存資源永遠會給最需要的程序使用。有了需求,自然會有解決辦法,程式設計師們為了拯救自己,可以做出各種匪夷所思的技術,他們發明了虛擬內存(Virtual Memory)。虛擬內存技術支持程序訪問比物理內存大得多的內存空間,也使得多個程序共享內存更加高效。物理內存由 RAM 晶片提供,虛擬內存則依靠透明的使用磁碟空間,使程序運行起來好像有了更大的內存空間。雖然是個錯覺,但是程序們都很開心,程式設計師們也很開心,但幸福的時光總是短暫的,過度分配內存(over-commit memory)的方式也會帶來麻煩。
當大部分程序都處於「工作狀態」的時候(例如高並發),大量的進程會向作業系統申請內存,這就導致了內存的需求加起來在某個瞬間超過了物理內存,怎麼處理這種情況呢?難不倒程式設計師,他們又為此設計了一位殺手,起名內存溢出殺手 —— OOM Killer。當內存溢出的時候,OOM Killer 會冷靜的挑一些進程出來,殘忍殺害,以保證整個系統正常運行。這時候的內存管理就像銀行擠兌,人們把現金存入銀行,收取一定的利息,平時只有少數人去銀行取現,銀行會拿人們存的錢去做更有價值的投資。但是,如果大部分人都去銀行取現,銀行是沒有那麼多現金的。擠兌,往往會導致銀行倒閉,畢竟銀行沒辦法請殺手幹掉那麼多的擠兌群眾。現在的 P2P 理財也是一個道理,投資者都去變現,無論是多麼良性的資產,一樣玩完。
OOM Killer 的調用鏈路大致是這樣的,分配內存,發現內存不足,這時候系統會調用 OOM Killer 的 out_of_memory 方法,通過 select_bad_process 找到評分最高的進程,幹掉。
每個進程的分數存儲在 /proc/$pid/oom_score,這個評分是程序通過各種因子計算出來的,比如進程起始佔用的虛擬內存,進程的子進程(如果有的話)對內存的使用情況,對 cpu 的使用時間,程序運行時間,進程間交互等。大的方向就是,你用內存越狠越頻繁,評分越高,當發生內存溢出的時候越容易被殺掉。
具體的代碼和算法可以參考 mm/oom_kill.c,注釋寫的很清楚。
https://github.com/torvalds/linux/blob/master/mm/oom_kill.c
這時候的另一個矛盾是,大部分重要的應用程式,都會頻繁的大量使用內存,如何避免重要的進程被少掉呢?OOM Killer 提供了一個分數調節機制。修改 /proc/$pid/oom_score_adj 的調整分數,可以降低該進程被 kill 的機率。由於 score 的分值範圍在 [-17, 15],本著分值越大約容易被殺死的原則,為一個進程設置 adj 為 -17,基本上可以保證該進程是安全的。
如果查看分數排名最高的進程呢?可以用下面這個腳本:
關於 Linux 內存管理更詳細的內容,可以參考這篇文章(或者直接去讀英文官方文檔)
http://learning-kernel.readthedocs.io/en/latest/mem-management.html
當然了,解決內存不足問題的好辦法之一是增加物理內存,內存多了,很多問題就不是問題了。隨著硬體的發展,之前遇到的很多軟體問題和程式設計師需要窮盡聰明才智解決的問題,都慢慢消失了,這也是一種生活。
最後一期互相:騰訊 2018雲 + 未來峰會將於 5 月 23 號在廣州召開,騰訊小馬哥會出席這個會議,據說張小龍也在現場。我和鳥哥等人會去參加個開發者專場,歡迎你一同前往。
如何把握雲計算紅利?來聽業界大咖乾貨(騰訊互選,點擊查看詳情)