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

myownstars發表於2015-10-24

背景:

某機器記憶體256G,安裝2例項mysql,每個 buffer_pool106G,總計212G

某套DB晚上10:00左右遷移到該環境,第2天早上10:00左右收到OOM kill簡訊,因swap空間不足一個Mysql例項被強制kill

該例項mysqld程式沒有被徹底清除,而是變成了殭屍程式,導致後續無法重啟該例項,最後重啟機器才解決。

 

調查:

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

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

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

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

 

延伸

1 mysql記憶體開銷

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

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

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

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

 

 

2 為什麼會發生swap

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

每個zone都有active_lru inactive_lru,每個lru又各分為anon匿名頁和file cache對映頁連結串列,總計4LRU

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

Linux會快取很多資料,譬如page cacheslab 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%左右。

 

參考資料

http://www.ibm.com/developerworks/cn/linux/l-cn-pagerecycle/index.html?ca=dat

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/15480802/viewspace-1815985/,如需轉載,請註明出處,否則將追究法律責任。

相關文章