MySQL針對Swap分割槽的運維注意點

散盡浮華發表於2016-06-08

 

Linux有很多很好的記憶體、IO排程機制,但是並不會適用於所有場景。對於運維人員來說,Linux比較讓人頭疼的一個地方是:它不會因為MySQL很重要就避免將分配給MySQL的地址空間對映到swap上。對於頻繁進行讀寫操作的系統而言,資料看似在記憶體而實際上在磁碟是非常糟糕的,響應時間的增長很可能直接拖垮整個系統。所以,作為運維人員,怎樣做到儘量避免MySQL慘遭Swap的毒手將顯得尤為重要!

SWAP是作業系統虛擬出來的一部分記憶體地址,它的物理儲存元件是磁碟。在備份資料或恢復資料時,檔案系統會向Linux系統請求大量的記憶體作為cache。在實體記憶體使用殆盡時候,為了確保程式執行,往往會將另外的一些佔用實體記憶體地址空間的程式對映到swap分割槽上。

作業系統設定swap的目的
程式執行的一個必要條件就是足夠的記憶體,而記憶體往往是系統裡面比較緊張的一種資源。為了滿足更多程式的要求,作業系統虛擬了一部分記憶體地址,並將之對映到swap上。對於程式來說,它只知道作業系統給自己分配了記憶體地址,但並不清楚這些記憶體地址到底對映到實體記憶體還是swap。
實體記憶體和swap在功能上是一樣的,只是因為物理儲存元件的不同(記憶體和磁碟),效能上有很大的差別。作業系統會根據程式使用記憶體的特點進行換入和換出,儘可能地把實體記憶體留給最需要它的程式。但是這種排程是按照預先設定的某種規則的,並不能完全符合程式的需要。一些特殊的程式(比如MySQL)希望自己的資料永遠寄存在實體記憶體裡,以便提供更高的效能。於是作業系統就設定了幾個api,以便為呼叫者提供"特殊服務"。

伺服器產生Swa分割槽的原因
1)copy一個大檔案,比如上百G的backup包
2)正在mysqldump以及mysql import一個很大的庫的時候。
3)大批量的併發操作的io writer和io read操作。

MySQL程式執行時,實體記憶體為MySQL分配了大量的實體地址空間,以提高執行的速率。為了避免在執行消耗大量記憶體的操作時將MySQL所擁有的部分實體記憶體地址空間對映到swap分割槽上(比如出現了MySQL伺服器Swap滿了100%導致db很慢很卡的現象),可做一下調整(解決辦法):
1)改系統核心引數/proc/sys/vm/swappiness。調整系統使用swap分割槽的傾向性,數值越低越傾向於釋放檔案系統的cache,不能避免Linux系統使用swap分割槽。swappiness=0表示最大限度使用實體記憶體,然後才是swap分割槽。swappiness=100表示積極使用swap分割槽,並且將記憶體上的資料及時的對映到swap分割槽上。

/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。將MySQL鎖定在記憶體中防止被swapping out。這個引數會強迫mysqld程式的地址空間一直被鎖定在實體記憶體上,對於os來說是非常霸道的一個要求。必須要用root帳號來啟動MySQL才能生效。

4)指定MySQL使用大頁記憶體(Large Page)。Linux上的大頁記憶體是不會被換出實體記憶體的,和memlock有異曲同工之妙。

5)臨時釋放鎖佔據的swap。

=========================================================

在Mysql資料庫維護中,會遇到的一個現象: MySQL記憶體持續增加,最高時實體記憶體消耗達到90%以上,導致swap使用率100%,進而造成記憶體不足,系統自動kill mysql程式。Mysql服務掛掉檢視Mysql的error日誌資訊:
[ERROR] InnoDB: Unable to lock /usr/local/mysql/var/ibdata1, error: 11
或者
InnoDB: mmap(137363456 bytes) failed; errno 12
2016-03-01 01:38:42 13064 [ERROR] InnoDB: Cannot allocate memory for the buffer pool
2016-03-01 01:38:42 13064 [ERROR] Plugin 'InnoDB' init function returned error.
2016-03-01 01:38:42 13064 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
2016-03-01 01:38:42 13064 [ERROR] Unknown/unsupported storage engine: InnoDB
2016-03-01 01:38:42 13064 [ERROR] Aborting

出現上面報錯的原因一般是系統記憶體資源不足造成的(error 11在mysql中是資源臨時不可用),解決方法是升級系統記憶體或者新增swap;

MySQL的記憶體消耗分為:
1)會話級別的記憶體消耗:如sort_buffer_size等,每個會話都會開闢一個sort_buffer_size來進行排序操作。
2)全域性的記憶體消耗:例如:innodb_buffer_pool_size等,全域性共享的記憶體段。

會話級的記憶體消耗可能是一個原因。關於會話級的記憶體消耗解釋如下:
read_buffer_size, sort_buffer_size, read_rnd_buffer_size, tmp_table_size這些引數在需要的時候才分配,操作後釋放。
這些會話級的記憶體,不管使用多少都分配該size的值,即使實際需要遠遠小於這些size。
每個執行緒可能會不止一次需要分配buffer,例如子查詢,每層都需要有自己的read_buffer,sort_buffer, tmp_table_size 等。
找到每次記憶體消耗峰值是不切實際的,因此建議可以用來衡量一下你實際修改一些變數值產生的反應,例如把 sort_buffer_size 
從1MB增加到4MB並且在max_connections為1000 的情況下,記憶體消耗增長峰值並不是你所計算的3000MB而是30MB。

首先在/etc/my.cnf的mysqld配置區域下增加下面一句(根據機器本身的記憶體配置來設定下面這個引數值):

[root@mysql01 ~]# vim /etc/my.cnf
[mysqld]
.......
innodb_buffer_pool_size = 128M

然後開啟Swap分割槽,重啟Mysql服務。

開啟SWAP分割槽的方法

1)建立用於交換分割槽的檔案(block_size、number_of_block 大小可以根據機器本身配置情況進行自定義,)
[root@mysql01 ~]# dd if=/dev/zero of=/mnt/swap bs=1M count=4096

2)設定交換分割槽檔案:
[root@mysql01 ~]# mkswap /mnt/swap

3)立即啟用交換分割槽檔案
[root@mysql01 ~]# swapon /mnt/swap

溫馨提示:
如果在/etc/rc.local中有"swapoff -a",則需要修改為"swapon -a"

4)設定開機時自啟用 SWAP 分割槽:
需要修改檔案 /etc/fstab 中的 SWAP 行,新增:
[root@mysql01 ~]# vim /etc/fstab
/mnt/swap swap swap defaults 0 0

5)修改swpapiness引數
在Linux系統中,可以通過檢視/proc/sys/vm/swappiness內容的值來確定系統對SWAP分割槽的使用原則。
當swappiness內容的值為0時,表示最大限度地使用實體記憶體,實體記憶體使用完畢後,才會使用SWAP分割槽。
當swappiness內容的值為100時,表示積極地使用SWAP分割槽,並且把記憶體中的資料及時地置換到SWAP分割槽。

檢視修改前為0,需要在實體記憶體使用完畢後才會使用SWAP分割槽:
[root@mysql01 ~]# echo 0 > /proc/sys/vm/swappiness

可以上面的方法臨時修改此引數,假設我們配置為空閒記憶體少於10%時才使用SWAP分割槽,則操作方法如下:
[root@mysql01 ~]# echo 10 > /proc/sys/vm/swappiness

若需要永久修改此配置,在系統重啟之後也生效的話,可以修改 /etc/sysctl.conf 檔案,並增加以下內容:  
[root@mysql01 ~]# vim /etc/sysctl.conf
vm.swappiness=10
[root@mysql01 ~]# sysctl -p

6)最後重啟mysql服務
[root@mysql01 ~]# /etc/init.d/mysqld restart

關閉SWAP分割槽的方法

當系統出現記憶體不足時,開啟 SWAP 可能會因頻繁換頁操作,導致 IO 效能下降。如果要關閉 SWAP,可以採用如下方法。

1)free -m 查詢 SWAP 分割槽設定:
[root@mysql01 ~]# free -m

2)使用命令 swapoff 關閉 SWAP,比如:
[root@mysql01 ~]# swapoff /mnt/swap

3)修改 /etc/fstab 檔案,刪除或註釋相關配置,取消SWAP的自動掛載:
[root@mysql01 ~]# vim /etc/fsta
#/mnt/swap swap swap defaults 0 0

4)通過 free -m  確認 SWAP 已經關閉。
[root@mysql01 ~]# free -m

5)swappiness 引數調整:
可以使用下述方法臨時修改此引數,這裡配置為 0%:
[root@mysql01 ~]# echo 0 >/proc/sys/vm/swappiness

若需要永久修改此配置,在系統重啟之後也生效的話,可以修改 /etc/sysctl.conf 檔案,並增加以下內容:
[root@mysql01 ~]# vim /etc/sysctl.conf
vm.swappiness=0
[root@mysql01 ~]# sysctl -p

=========================================================
來看看曾經碰到的一個由於MySQL記憶體交換區引起的一場事故

 事故現象:公司的一個業務系統程式會呼叫大量的SQL,一天,發現MySQL的負載極其不穩定,尤其是Slave的負載有點猛,然後經過討論準備再加一臺Slave,。另加了一臺Slave後,發現Slave的負載確實都回歸正常了,本以為息事寧人,但是過了兩個小時後,新新增的Slave的負載暴增至100以上!原來的Slave服務則顯示正常。大概過了兩個小時,新增加的Slave的負載又迴歸正常了,但是又過了兩個小時後,負責又飆升至100以上!於是乎,趕緊Troubleshooting!過程如下:

1)考慮到新增的server,因為記憶體,CPU等硬體的配置和原來資料庫的server都不一樣(新增的Slave比原來的Slave的記憶體少了一半),必然配置引數的值也會不同。所以就從MySQl的配置檔案查起,如:sort_buffer_size的大小(因為考慮到有許多SQL包含排序),join_buffer_size(用於連線的快取的大小),max_connections(最大連線數,可是通過show processlist;發現也沒有超過設定的值),innodb_buffer_pool_size(確認是否為實體記憶體的合適比例)等等。

2)使用free -m檢視記憶體時,發現SWAP既然使用了500MB!通過vmsata,發現si和so的值不斷的變化,可以肯定的是發生了記憶體交換。原來是SWAP搗的蛋!

記憶體交換區:當作業系統因為沒有足夠的記憶體而將一些虛擬記憶體寫到磁碟就會發生記憶體交換。

記憶體交換對MySQL效能影響是極其糟糕的。它破壞了快取在記憶體的目的,並且相對於使用很小的記憶體做快取,使用交換區的效能更差。MySQL和儲存引擎有很多演算法來區別對待記憶體中的資料和硬碟上的資料,因為一般都是假設記憶體資料訪問代價更低。

因為記憶體交換對使用者程式不可見,MySQL(或儲存引擎)並不知道資料實際上已經移動到磁碟,還會以為仍然在記憶體中呢。

結果會導致很差的效能。例如。若儲存引擎認為資料依然在記憶體,可能覺得為"短暫"的記憶體操作鎖定一個全域性互斥變數(例如,InnoDB緩衝池Mutex)是OK的。如果這個操作實際上引起了硬碟I/O,直到I/O操作完成前任何操作都會被掛起。這意味著記憶體交換比直接做硬碟I/O操作還要糟糕。

在Linux上,可以用vmstat來監控記憶體交換。最好檢視si和so列報告的記憶體交換I/O活動,這比看swapd列報告的交換區利用率更重要。我們都喜歡si和so列的值為0,並且一定要保證它們低於每秒10塊。

可以通過正確地配置MySQL緩衝來解決大部分記憶體交換問題,但是有時作業系統的虛擬記憶體系統還是會決定交換MySQL記憶體。這通常發生在作業系統看到MySQL發出了大量I/O,因此嘗試增加檔案快取來儲存更多資料時。如果沒有足夠的記憶體,有些東西就必須交換出去,有些可能就是MySQL本身。

有些人主張完全禁用交換檔案。這樣做是很危險的,因為禁用記憶體交換就相當於給虛擬記憶體設定了一個不可動搖的限制。如果MySQL需要臨時使用很大一塊記憶體,或者有很耗記憶體的程式執行在同一臺server上(如夜間的批量任務),MySQL可能會記憶體溢位,崩潰,或者被作業系統kill掉。

作業系統通常允許對虛擬記憶體和I/O進行一些控制。最基本的方法就是修改/proc/sys/vm/swappiness為一個很小的值,如0或1。這等同於告訴核心除非虛擬記憶體完全滿了,否則不要使用交換區。下面是如何檢查這個值的例子:

$ cat /proc/sys/vm/swappiness
60

這個值顯示為60,這是預設的設定(範圍是0~100)。對於伺服器而言這是個很糟糕的預設值。伺服器應該設定為0:

$ echo 0 > /proc/sys/vm/swappiness

另一個選項是修改儲存引擎怎麼讀取和寫入資料。使用innodb_flush_method=O_DIRECT,減輕I/O壓力。DIRECT I/O並不快取,因此作業系統並不能把MySQL視為增加檔案快取的原因。這個引數只對InnoDB有效。你也可以使用大頁,不參與換入換出,這對MyISAM和InnoDB都有效。

另一個選擇是使用MySQL的memlock配置項,可以把MySQL鎖定在記憶體。這可以避免交換,但是也可能帶來危險:如果沒有足夠的可鎖定記憶體,MySQL在嘗試分配更多記憶體時就會崩潰。

解決問題:

第一種方法:修改系統對虛擬記憶體的控制
[root@mysql01 ~]# echo 0 > /proc/sys/vm/swappiness

#要想永久生效,將其配置寫入/etc/sysctl.conf檔案中
[root@mysql01 ~]# echo "vm.swappiness=0" >> /etc/sysctl.conf
#令其立即生效
[root@mysql01 ~]# sysctl -p

第二種方法:修改innodb_flush_method引數
#注意innodb_flush_method是個全域性變數,並且不支援動態修改,所以修改配置檔案,重啟MySQL
[root@mysql01 ~]# vim /etc/my.cnf
innodb_flush_method=O_DIRECT

#新增其引數的配置,如果線上正在執行的資料庫,就要先:
mysql> stop slave;

#然後重啟MySQL
[root@mysql01 ~]# /etc/init.d/mysqld restart

對於修改memlock配置項,不推薦。

結果:新增加的slave負載正常。swap的使用也降到了10MB的樣子。

最後說下如何檢視那個程式佔用swap?

第一步:
[root@mysql01 ~]# top

第二步:
按大寫的O

第三步:
輸入小寫字母p

第四步:
回車

顯示的結果如下圖:

相關文章