最近有位 VPS 客戶抱怨 MySQL 平白無故掛掉,還有位客戶抱怨 VPS 常常死機,登錄到終端看了一下,都是常見的 Out of memory 問題。這一般是由於某時刻應用程序大量請求內存致使系統內存不足形成的,這一般會觸發 Linux 內核裏的 Out of Memory (OOM) killer,OOM killer 會殺掉某個進程以騰出內存留給系統用,不致於讓系統馬上崩潰。若是檢查相關的日誌文件(/var/log/messages)就會看到下面相似的 Out of memory: Kill process 信息:node
... Out of memory: Kill process 9682 (mysqld) score 9 or sacrifice child Killed process 9682, UID 27, (mysqld) total-vm:47388kB, anon-rss:3744kB, file-rss:80kB httpd invoked oom-killer: gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0 httpd cpuset=/ mems_allowed=0 Pid: 8911, comm: httpd Not tainted 2.6.32-279.1.1.el6.i686 #1 ... 21556 total pagecache pages 21049 pages in swap cache Swap cache stats: add 12819103, delete 12798054, find 3188096/4634617 Free swap = 0kB Total swap = 524280kB 131071 pages RAM 0 pages HighMem 3673 pages reserved 67960 pages shared 124940 pages non-shared
Linux 內核根據應用程序的要求分配內存,一般來講應用程序分配了內存可是並無實際所有使用,爲了提升性能,這部分沒用的內存能夠留做它用,這部份內存是屬於每 個進程的,內核直接回收利用的話比較麻煩,因此內核採用一種過分分配內存(over-commit memory)的辦法來間接利用這部分 「空閒」 的內存,提升總體內存的使用效率。通常來講這樣作沒有問題,但當大多數應用程序都消耗完本身的內存的時候麻煩就來了,由於這些應用程序的內存需求加起來超 出了物理內存(包括 swap)的容量,內核(OOM killer)必須殺掉一些進程才能騰出空間保障系統正常運行。用銀行的例子來說可能更容易懂一些,部分人取錢的時候銀行不怕,銀行有足夠的存款應付,當 全國人民(或者絕大多數)都取錢並且每一個人都想把本身錢取完的時候銀行的麻煩就來了,銀行其實是沒有這麼多錢給你們取的。mysql
內核檢測到系統內存不足、挑選並殺掉某個進程的過程能夠參考內核源代碼 linux/mm/oom_kill.c, 當系統內存不足的時候,out_of_memory() 被觸發,而後調用 select_bad_process() 選擇一個 「bad」 進程殺掉,如何判斷和選擇一個 「bad」 進程呢,總不能隨機選吧?挑選的過程由 oom_badness() 決定,挑選的算法和想法都很簡單很樸實:最 bad 的那個進程就是那個最佔用內存的進程。linux
/** * oom_badness - heuristic function to determine which candidate task to kill * @p: task struct of which task we should calculate * @totalpages: total present RAM allowed for page allocation * * The heuristic for determining which task to kill is made to be as simple and * predictable as possible. The goal is to return the highest value for the * task consuming the most memory to avoid subsequent oom failures. */ unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg, const nodemask_t *nodemask, unsigned long totalpages) { long points; long adj; if (oom_unkillable_task(p, memcg, nodemask)) return 0; p = find_lock_task_mm(p); if (!p) return 0; adj = (long)p->signal->oom_score_adj; if (adj == OOM_SCORE_ADJ_MIN) { task_unlock(p); return 0; } /* * The baseline for the badness score is the proportion of RAM that each * task's rss, pagetable and swap space use. */ points = get_mm_rss(p->mm) + p->mm->nr_ptes + get_mm_counter(p->mm, MM_SWAPENTS); task_unlock(p); /* * Root processes get 3% bonus, just like the __vm_enough_memory() * implementation used by LSMs. */ if (has_capability_noaudit(p, CAP_SYS_ADMIN)) adj -= 30; /* Normalize to oom_score_adj units */ adj *= totalpages / 1000; points += adj; /* * Never return 0 for an eligible task regardless of the root bonus and * oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here). */ return points > 0 ? points : 1; }
理解了這個算法咱們就理解了爲啥 MySQL 躺着也能中槍了,由於它的體積老是最大(通常來講它在系統上佔用內存最多),因此若是 Out of Memeory (OOM) 的話老是不幸第一個被 kill 掉。解決這個問題最簡單的辦法就是增長內存,或者想辦法優化 MySQL 使其佔用更少的內存,除了優化 MySQL 外還能夠優化系統,讓系統儘量使用少的內存以便應用程序(如 MySQL) 能使用更多的內存,還有一個臨時的辦法就是調整內核參數,讓 MySQL 進程不容易被 OOM killer 發現。git
咱們能夠經過一些內核參數來調整 OOM killer 的行爲,避免系統在那裏不停的殺進程。好比咱們能夠在觸發 OOM 後馬上觸發 kernel panic,kernel panic 10秒後自動重啓系統。github
# sysctl -w vm.panic_on_oom=1 vm.panic_on_oom = 1 # sysctl -w kernel.panic=10 kernel.panic = 10 # echo "vm.panic_on_oom=1" >> /etc/sysctl.conf # echo "kernel.panic=10" >> /etc/sysctl.conf
從上面的 oom_kill.c 代碼裏能夠看到 oom_badness() 給每一個進程打分,根據 points 的高低來決定殺哪一個進程,這個 points 能夠根據 adj 調節,root 權限的進程一般被認爲很重要,不該該被輕易殺掉,因此打分的時候能夠獲得 3% 的優惠(adj -= 30; 分數越低越不容易被殺掉)。咱們能夠在用戶空間經過操做每一個進程的 oom_adj 內核參數來決定哪些進程不這麼容易被 OOM killer 選中殺掉。好比,若是不想 MySQL 進程被輕易殺掉的話能夠找到 MySQL 運行的進程號後,調整 oom_score_adj 爲 -15(注意 points 越小越不容易被殺):算法
# ps aux | grep mysqld mysql 2196 1.6 2.1 623800 44876 ? Ssl 09:42 0:00 /usr/sbin/mysqld # cat /proc/2196/oom_score_adj 0 # echo -15 > /proc/2196/oom_score_adj
固然,若是須要的話能夠徹底關閉 OOM killer(不推薦用在生產環境):sql
# sysctl -w vm.overcommit_memory=2 # echo "vm.overcommit_memory=2" >>