swap空間不足導致MySQL被OOM kill案例

來源:互聯網
上載者:User

swap空間不足導致MySQL被OOM kill案例

背景:

  • 某機器記憶體256G,安裝2執行個體mysql,每個 buffer_pool各106G,總計212G;
  • 某套DB晚上10:00左右遷移到該環境,第2天早上10:00左右收到OOM kill簡訊,因swap空間不足一個Mysql執行個體被強制kill;
  • 該執行個體mysqld進程沒有被徹底清除,而是變成了殭屍進程,導致後續無法重啟該執行個體,最後重啟機器才解決。

調查:

為oom kill後的top輸出,因為該mysqld變為殭屍進程故一直沒有釋放記憶體。

mysql的BP設定為 106G,但是其RES分別達到125G和119G,加起來接近機器實體記憶體上限,而機器swap只有7G且被消耗完畢。

至此原因已經很清晰,解決方案也很簡單,將BP調小90G。

註:自調整截至目前超過10天,沒有再發生類似故障。

延伸

1 mysql記憶體開銷

Innodb_buffer_pool_size定義了緩衝池的大小,但是緩衝池本身需要額外的資料結構進行管理。

比如,緩衝池每個page都需要一個buf_block_t管理,這部分記憶體沒有計入參數。

各種額外消耗加起來約佔整個BP的8%,也有資料說是10%,具體可參看http://mysqlha.blogspot.co.uk/2008/11/innodb-memory-overhead.html。

這些只是global buffer的開銷,加上session buffer,Mysql所需的記憶體只會更高。

2 為什麼會發生swap

首先大致說一下linux的記憶體管理,numa架構下linux記憶體被分為多個node,非numa則只有1個,由pg_data_t描述,每個node又分為3個zone,由zone_struct結構體描述。

每個zone都有active_lru和 inactive_lru,每個lru又各分為anon匿名頁和file cache映射頁鏈表,總計4個LRU;

zone同時定義了pages_low,pages_min和pages_high,當zone可用記憶體小於pages_low時喚醒kswapd回收記憶體,而當其小於pages_min時則以同步方式喚醒kswapd,直到zone可用記憶體達到pages_high為止;

Linux會緩衝很多資料,譬如page cache和slab cache,這部分記憶體在回收時會先同步到磁碟然後直接重用,而對於其他記憶體頁,諸如使用者態地址空間的匿名頁,以及IPC共用記憶體區的頁,只能將其置換到swap分區,不可直接回收。

OS何時回收記憶體?

1 定期回收:kswapd定期喚醒,當zone空閑記憶體小於pages_low則進行頁面回收,小於pages_min則以同步方式回收;

2 直接回收:linux為使用者進程分配記憶體或者建立緩衝區,而當前系統又沒有足夠多實體記憶體時,則linux會進行頁面回收;當OS嘗試記憶體回收後仍無法擷取足夠多的頁面,則調用find_bad_process並進行OOM kill;

不管哪種回收方式,最後都調用shrink_list(),對4個鏈表的掃描邏輯定義在vmscan.c中的get_scan_count函數內,其變數scan_balance決定了要回收哪個lru的記憶體,大致邏輯如下:

1. 如果系統禁用了swap或者沒有swap空間,則只掃描file based的鏈表,即不進行匿名頁鏈表掃描

if (!sc->may_swap || (get_nr_swap_pages() <= 0)) {

scan_balance = SCAN_FILE;

goto out;

}

2. 如果當前進行的不是全域頁回收,並且swappiness=0,則不進行匿名頁鏈表掃描

if (!global_reclaim(sc) && !vmscan_swappiness(sc)) {

scan_balance = SCAN_FILE;

goto out;

}

3. 如果是全域頁回收,並且空閑記憶體和file based鏈表page數目相加都小於zone->pages_high,則進行匿名頁回收,即便swappiness=0,系統也會進行swap

if (global_reclaim(sc)) {

unsigned long zonefile;

unsigned long zonefree;

zonefree = zone_page_state(zone, NR_FREE_PAGES);

zonefile = zone_page_state(zone, NR_ACTIVE_FILE) +

zone_page_state(zone, NR_INACTIVE_FILE);

if (unlikely(zonefile + zonefree <= high_wmark_pages(zone))) {

scan_balance = SCAN_ANON;

goto out;

}

}

4. 如果系統inactive file鏈表比較充足,則不考慮進行匿名頁的回收,即不進行swap

if (!inactive_file_is_low(lruvec)) {

scan_balance = SCAN_FILE;

goto out;

}

至此,我們可以大致瞭解swappiness=0的作用,其並不能完全禁止swap。

何時觸發OOM kill?

當系統記憶體被消耗殆盡,swap分區也被填滿的時候,核心無法分配到新的空閑記憶體,便會啟動OOM刪除程式;

其核心調用路徑為out_of_memory() – select_bad_process() – oom_kill_process()

其中select_bad_process()負責挑選待殺死的進程,其掃描系統中的每一個進程並調用oom_badness(),該API邏輯如下:

1 擷取進程的oom_score_adj,如果其等於OOM_SCORE_ADJ_MIN即-1000則不Kill,該參數由/proc/NNN/ oom_score_adj記錄,可手工修改

adj = (long)p->signal->oom_score_adj;

if (adj == OOM_SCORE_ADJ_MIN) {

task_unlock(p);

return 0;

}

2 根據該進程消耗的記憶體計算分數,如果是root進程則乘以3%,盡量避免其被Kill,最後將points返回

points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) +

atomic_long_read(&p->mm->nr_ptes) + mm_nr_pmds(p->mm);

task_unlock(p);

if (has_capability_noaudit(p, CAP_SYS_ADMIN))

points -= (points * 3) / 100;

select_bad_process()通過比較每一個進程的oom_badness()傳回值,找出得分最高且不是線程組leader的進程,將其返回給out_of_memory(),由其調用oom_kill_process()發送sigkill訊號進行撲殺。

除了殺死進程,Linux可以選擇在發生OOM時直接panic,當vm.panic_on_oom=1時成立

結束語

至此我們可以大致瞭解swappiness=0的意義,以及OOM kill發生的原因,為避免此行為應盡量留出充足的記憶體給OS,一般應為實體記憶體的20%左右。

本文永久更新連結地址:

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.