Linux有很多很好的內存、IO調度機制,但是並不會適用於所有場景。對於DBA來說Linux比較讓人頭疼的一個地方是,它不會因為MySQL很重要就避免將分配給MySQL的地址空間映射到swap上。對於頻繁進行讀寫操作的系統而言,數據看似在內存而實際上在磁盤是非常糟糕的,響應時間的增長很可能直接拖垮整個系統。這篇blog主要講講我們作為DBA,怎樣盡量避免MySQL慘遭swap的毒手。
首先我們要了解點基礎的東西,比如說為什麼會產生swap。假設我們的物理內存是16G,swap是4G。如果MySQL本身已經占用了12G物理內存,而同時其他程序或者系統模塊又需要6G內存,這時候操作系統就可能把MySQL所擁有的一部分地址空間映射到swap上去。
cp一個大文件,或用mysqldump導出一個很大的數據庫的時候,文件系統往往會向Linux申請大量的內存作為cache,一不小心就會導致L使用swap。這個情景比較常見,以下是最簡單的三個調整方法:
1、/proc/sys/vm/swappiness的內容改成0(臨時),/etc/sysctl.conf上添加vm.swappiness=0(永久)
這個參數決定了Linux是傾向於使用swap,還是傾向於釋放文件系統cache。在內存緊張的情況下,數值越低越傾向於釋放文件系統cache。
當然,這個參數只能減少使用swap的概率,並不能避免Linux使用swap。
2、修改MySQL的配置參數innodb_flush_method,開啟O_DIRECT模式。
這種情況下,InnoDB的buffer pool會直接繞過文件系統cache來訪問磁盤,但是redo log依舊會使用文件系統cache。值得注意的是,Redo log是覆寫模式的,即使使用了文件系統的cache,也不會占用太多。
3、添加MySQL的配置參數memlock
這個參數會強迫mysqld進程的地址空間一直被鎖定在物理內存上,對於os來說是非常霸道的一個要求。必須要用root帳號來啟動MySQL才能生效。
還有一個比較復雜的方法,指定MySQL使用大頁內存(Large Page)。Linux上的大頁內存是不會被換出物理內存的,和memlock有異曲同工之妙。具體的配置方法可以參考:http://harrison-fisk.blogspot.com/2009/01/enabling-innodb-large-pages-on-linux.html
之前介紹了MySQL如何避免使用swap的四個方法。這裡需要補充一下原理和實現機制,對於Linux api不感興趣的同學可以直接跳過。
一、操作系統設置swap的目的
程序運行的一個必要條件就是足夠的內存,而內存往往是系統裡面比較緊張的一種資源。為了滿足更多程序的要求,操作系統虛擬了一部分內存地址,並將之映射到swap上。對於程序來說,它只知道操作系統給自己分配了內存地址,但並不清楚這些內存地址到底映射到物理內存還是swap。
物理內存和swap在功能上是一樣的,只是因為物理存儲元件的不同(內存和磁盤),性能上有很大的差別。操作系統會根據程序使用內存的特點進行換入和換出,盡可能地把物理內存留給最需要它的程序。但是這種調度是按照預先設定的某種規則的,並不能完全符合程序的需要。一些特殊的程序(比如MySQL)希望自己的數據永遠寄存在物理內存裡,以便提供更高的性能。於是操作系統就設置了幾個api,以便為調用者提供“特殊服務”。
二、Linux提供的幾個api
1、mlockall()和munlockall()
這一對函數,可以讓調用者的地址空間常駐物理內存,也可以在需要的時候將此特權取消。mlockall()的flag位可以是MCL_CURRENT和MCL_FUTURE的任意組合,分別代表了“保持已分配的地址空間常駐物理內存”和“保持未來分配的地址空間常駐物理內存”。對於Linux來說,這對函數是非常霸道的,只有root用戶才有權限調用。
2、shmget()和shmat()
這一對函數,可以向操作系統申請使用大頁內存(Large Page)。大頁內存的特點是預分配和永駐物理內存,因為使用了共享內存段的方式,page table有可能會比傳統的小頁分配方式更小。對於多進程共享內存的程序(比如ORACLE),大頁內存能夠節省很多page table開銷;而對於MySQL來說,性能和資源開銷都沒有顯著變化,好處就在於減少了內存地址被映射到swap上的可能。至於為什麼是減少,而不是完全避免,之後再講解。
3、O_DIRECT和posix_memalign()
以上兩個方法都不會減少內存的使用量,調用者的本意是獲取更高的系統特權,而不是節約系統資源。O_DIRECT是一種更加理想化的方式,通過避免double buffer,節省了文件系統cache的開銷,最終減少swap的使用率。O_DIRECT是Linux IO調度相關的標志,在open函數裡面調用。通過O_DIRECT標志打開的文件,讀寫都不會用到文件系統的cache。傳統的數據庫(ORACLE、MySQL)基本都有O_DIRECT相關的開關,在提高性能的同時,也減少了內存的使用。至於posix_memalign(),是用來申請對齊的內存地址的。只有用posix_memalign()申請的內存地址,才能用來讀寫O_DIRECT模式下的文件描述符。
4、madvise()和fadvise()
這對函數也是比較溫和的,可以將調用者對數據訪問模式的預期傳遞給Linux,以期得到更好的性能。
我們比較感興趣的是MADV_DONTNEED和FADV_NOREUSE這兩個flag。前者會建議Linux釋放指定的內存區域,而後者會建議文件系統釋放指定文件所占用的cache。
三、MySQL內存使用相關的一些代碼
1、memlock
在MySQL的源碼目錄裡面查詢memlock,可以知道這個參數的作用是使MySQL調用mlockall()。在源碼裡面匹配可以得知NDB、MyISAM和mysqld都調用了mlockall()。NDB是可以獨立於MySQL而存在的存儲引擎,此處按下不表。mysqld調用mlockall()的方式有點出乎意料,在init_server_components()函數裡傳給mlockall()的flag是MCL_CURRENT,也就是說之後申請的內存一概不用鎖住。再看看MyISAM的調用順序是:mlockall() <- lock_memory() <- mi_repair(),MyISAM只有修復的時候會調用mlockall()函數。
2、large-pages
根據Linux的內核文檔,大頁內存有兩種方法可以用到:一種是創建hugetlb類型的文件,並將它mmap到程序的內存地址裡面,然後進行正常的讀寫操作。另外一種是之前說到的shmget()+shmat(),也正是MySQL采用的方式。在MySQL的源碼目錄裡面匹配shmget,可以發現BDB、NDB、InnoDB、MyISAM都調用了這個函數。接著看一下比較常用的InnoDB和MyISAM引擎。
在InnoDB裡面可以找到os_mem_alloc_large()調用了shmget(),而調用os_mem_alloc_large()的函數只有buf_pool_init()——InnoDB Buffer Pool的初始化函數。根據觀察得到的結論是,InnoDB會根據配置參數在Buffer Pool裡面使用大頁內存,Redo log貌似就沒有這個待遇了。
對於MyISAM,在storage層級的代碼裡面找不到對shmget()的直接調用。這是因為MyISAM是MySQL的原生存儲引擎,很多函數存放在上一層的mysys目錄裡面。通過搜索shmget(),我們可以找到MyISAM的調用順序是這樣的:shmget() <- my_large_malloc_int() <- my_large_malloc() <- init_key_cache()。也就是說MyISAM只有索引緩存用到了大頁內存,這是很容易理解,因為MyISAM的數據是直接扔給文件系統做緩存的,沒法使用大頁內存。
3、innodb_flush_method
O_DIRECT是BDB、NDB、InnoDB特有的參數,在這裡只討論InnoDB這個比較常見的引擎。在InnoDB的源碼目錄裡面匹配O_DIRECT,很容易找到一個叫做os_file_set_nocache()的函數,而這個函數作用是將文件的打開方式改為O_DIRECT模式。再跟蹤一下,會發現只有os_file_create()函數調用了os_file_set_nocache()。雖然函數名裡面還有create,實際上os_file_create()會根據傳入參數的不同,選擇打開或者新建一個文件。同時os_file_create()還會根據MySQL的配置,來調用os_file_set_nocache()關閉文件系統的相應cache。在os_file_create()函數裡面有如下一段代碼:
/* We disable OS caching (O_DIRECT) only on data files */
if (type != OS_LOG_FILE &&
srv_unix_file_flush_method == SRV_UNIX_O_DIRECT)
{
os_file_set_nocache(file, name, mode_str);
}
這段代碼的意思是,只有InnoDB的數據文件有資格使用O_DIRECT模式,Redo log是不能使用的。
以上的分析基於5.0.85版本的原版MySQL,InnoDB是Innobase。
版本不同情況下可能會有一些出入,歡迎參與討論。