王棟:攜程技術保障中心數據庫專家,對數據庫疑難問題的排查和數據庫自動化智能化運維工具的開發有強烈的興趣。node
咱們知道當mysqld進程使用到SWAP時,就會嚴重影響到MySQL的性能。SWAP的問題比較複雜,本文會從SWAP的原理開始,分享咱們碰到的案例和分析思路。mysql
swap是把一部分磁盤空間或文件,看成內存來使用。它有換出和換入兩種方式,換出是進程把不活躍的內存數據存儲到磁盤上,並釋放數據佔用的內存空間,換入是進程再次訪問這部分數據的時候,從磁盤讀到內存中。linux
swap擴展了內存空間,是爲了回收內存。內存回收的機制,一種是當內存分配沒有足夠的空間時,系統須要回收一部份內存,稱爲直接內存回收。另外還有一個專門的kswapd0進程用來按期回收內存。爲了衡量內存的使用狀況,定義了三個內存閥值,分爲頁最小水位(min)、頁低水位(low)、頁高水位(high)sql
執行下面命令,能夠看到水位線對應的值,以下圖所示
數據庫
cat /proc/zoneinfo |grep -E "Node|pages free|nr_inactive_anon|nr_inactive_file|min|low|high"|grep -v "high:"
有些案例咱們發現系統還有大量剩餘空間的狀況下,已經使用了swap。這正是NUMA架構致使的。NUMA架構下每一個Node都有本地的內存空間,Node間內存使用不均衡,當某個Node的內存不足時,就可能致使swap的產生。vim
咱們大概理解了內存回收的機制,回收的內存包括文件頁和匿名頁。對文件頁的回收就是直接回收緩存,或者把髒頁寫回到磁盤再進行回收。對匿名頁的回收,就是經過swap,將數據寫入磁盤後再釋放內存。
經過調整/proc/sys/vm/swappiness的值,能夠調整使用swap的積極程度,swappiness值從0-100,值越小,傾向於回收文件頁,儘可能少的使用swap。咱們最初將這個值調整爲1,但發現並不能避免swap的產生。實際上即便將這個值設置0,當知足file+free<=high時,仍是會發生swap。緩存
在NUMA開啓的狀況,因爲NUMA節點間內存使用不均衡,可能致使swap,解決這個問題主要有下面一些方案
服務器
一、 在mysqld_safe腳本中加上「numactl –interleave all」來啓動mysqld
二、 Linux Kernel啓動參數中加上numa=off,須要重啓服務器
三、 在BIOS層面關閉NUMA
四、 MySQL 5.6.27/5.7.9開始引用innodb_numa_interleave選項
一、 yum install numactl -y 二、修改/usr/bin/mysqld_safe文件 cmd="`mysqld_ld_preload_text`$NOHUP_NICENESS"下新增一條腳本 cmd="/usr/bin/numactl --interleave all $cmd" 三、service mysql stop 四、寫入硬盤,防止數據丟失 sync;sync;sync 五、延遲10秒 sleep 10 六、清理pagecache、dentries和inodes sysctl -q -w vm.drop_caches=3 七、service mysql start 八、驗證numactl –interleave all是否生效,能夠經過下面命令,interleave_hit是採用interleave策略從該節點分配的次數,沒有啓動interleave策略的服務器,這個值會很低 numastat -mn -p `pidof mysqld`
至此咱們MySQL5.6的服務器經過上面方案解決了因爲NUMA Node間內存分配不均致使的swap的問題。對於MySQL5.7.23版本的服務器,咱們使用了innodb_numa_interleave選項,但問題並無完全解決。架構
在開啓innodb_numa_interleave選項的服務器中,仍然會存在NUMA Node間內存分配不均衡的問題,會致使swap產生。針對這個問題作了進一步分析:
一、 MySQL 版本爲5.7.23,已經開啓了innodb_numa_interleave
二、 使用命令查看mysqld進程的內存使用狀況,numastat -mn `pidof mysqld`
能夠看出Node 0使用了約122.5G內存,Node 1使用了約68.2G內存,其中Node0上的可用空間只剩566M,若是後面申請Node 0節點分配內存不足,就可能產生swap
app
Per-node process memory usage (in MBs) for PID 1801 (mysqld)
Node 0 Node 1 Total
--------------- --------------- ---------------
Huge 0.00 0.00 0.00
Heap 0.00 0.00 0.00
Stack 0.01 0.07 0.09
Private 125479.61 69856.82 195336.43
---------------- --------------- --------------- ---------------
Total 125479.62 69856.90 195336.52
三、是innodb_numa_interleave沒有生效嗎,經過分析/proc/1801/numa_maps文件能夠進一步查看mysqld進程的內存分配狀況
以其中一條記錄爲例,
7f9067850000 表示內存的虛擬地址
interleave:0-1 表示內存所用的NUMA策略,這裏使用了Interleave方式
anon=5734148 匿名頁數量
dirty=5734148 髒頁數量
active=5728403 活動列表頁面的數量
N0=3607212 N1=2126936 節點0、1分配的頁面數量
kernelpagesize_kB=4 頁面大小爲4K
7f9067850000 interleave:0-1 anon=5734148 dirty=5734148 active=5728403 N0=3607212 N1=2126936 kernelpagesize_kB=4
四、經過解析上面文件,對Node 0和Node 1節點分配的頁面數量作統計,能夠計算出Node 0經過interleave方式分配了約114.4G內存,Node 1經過interleave方式分配了約64.7G內存
說明innodb_numa_interleave開關是實際生效的,可是即便mysql使用了interleave的分配方式,仍然存在不均衡的問題
五、經過innodb_numa_interleave相關的源碼,能夠看出當開關開啓時,MySQL調用linux的set_mempolicy函數指定MPOL_INTERLEAVE策略跨節點來分配內存set_mempolicy(MPOL_INTERLEAVE, numa_all_nodes_ptr->maskp, numa_all_nodes_ptr->size)
當開關關閉時,set_mempolicy(MPOL_DEFAULT, NULL, 0),使用默認的本地分配策略
my_bool srv_numa_interleave = FALSE;
#ifdef HAVE_LIBNUMA
#include <numa.h>
#include <numaif.h>
struct set_numa_interleave_t
{
set_numa_interleave_t()
{
if (srv_numa_interleave) {
ib::info() << "Setting NUMA memory policy to"
" MPOL_INTERLEAVE";
if (set_mempolicy(MPOL_INTERLEAVE,
numa_all_nodes_ptr->maskp,
numa_all_nodes_ptr->size) != 0) {
ib::warn() << "Failed to set NUMA memory"
" policy to MPOL_INTERLEAVE: "
<< strerror(errno);
}
}
}
~set_numa_interleave_t()
{
if (srv_numa_interleave) {
ib::info() << "Setting NUMA memory policy to"
" MPOL_DEFAULT";
if (set_mempolicy(MPOL_DEFAULT, NULL, 0) != 0) {
ib::warn() << "Failed to set NUMA memory"
" policy to MPOL_DEFAULT: "
<< strerror(errno);
}
} }};
一、 修改systemd配置文件,刪除my.cnf中innodb_numa_interleave=on開關配置,重啓MySQL服務
/usr/bin/numactl --interleave=all /usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid $MYSQLD_OPTS
二、 運行select count(*) from test.sbtest1語句,這個表中有2億條記錄,運行14分鐘,會將表中的數據讀到buffer pool中
三、運行結束後,分析numa_maps文件能夠看到mysqld進程採用了interleave跨節點訪問的分配方式,兩個Node間分配的內存大小基本一致
7f9a3c5b3000 interleave:0-1 anon=1688811 dirty=1688811 N0=842613 N1=846198 kernelpagesize_kB=4
7f9a3c5b3000 interleave:0-1 anon=2497435 dirty=2497435 N0=1247949 N1=1249486 kernelpagesize_kB=4
四、mysqld進程總的分配也是均衡的
一、增長my.cnf中innodb_numa_interleave=on開關配置,重啓MySQL服務,執行與場景一相關的SQL語句
二、運行結束後,分析numa_maps文件能夠看到mysqld進程採用interleave方式分配的在不一樣Node間是基本平衡的
7f71d8d98000 interleave:0-1 anon=222792 dirty=222792 N0=111652 N1=111140 kernelpagesize_kB=4
7f74a2e14000 interleave:0-1 anon=214208 dirty=214208 N0=107104 N1=107104 kernelpagesize_kB=4
7f776ce90000 interleave:0-1 anon=218128 dirty=218128 N0=108808 N1=109320 kernelpagesize_kB=4
三、不過仍有部份內存使用了default的本地分配策略,這部份內存所有分配到了Node 0上
7f31daead000 default anon=169472 dirty=169472 N0=169472 kernelpagesize_kB=4
四、最終mysqld進程分配的內存Node 0 比Node 1大了約1G
MySQL5.7版本再也不使用mysqld_safe文件,因此啓用numactl –interleave=all的方式,與MySQL 5.6的方法不一樣,總結以下:
一、修改vim /etc/my.cnf文件,刪除innodb_numa_interleave配置項 二、修改systemd 的本地配置文件,vim /usr/lib/systemd/system/mysqld.service,增長/usr/bin/numactl --interleave=all命令 # Start main service ExecStart=/usr/bin/numactl --interleave=all /usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid $MYSQLD_OPTS 三、中止MySQL服務 systemctl stop mysqld.service 四、從新加載配置文件 systemctl daemon-reload 五、寫入硬盤,防止數據丟失 sync;sync;sync 六、延遲10秒 sleep 10 七、清理pagecache、dentries和inodes sysctl -q -w vm.drop_caches=3 八、啓動MySQL服務 systemctl start mysqld.service 九、驗證是否生效, 首先確認show global variables like ' innodb_numa_interleave';開關爲關閉狀態 正常狀況下mysqld進程會所有采用interleave跨節點訪問的分配方式,若是能夠查詢到其餘訪問方式的信息,表示interleave方式沒有正常生效 less /proc/`pidof mysqld`/numa_maps|grep -v 'interleave'
numactl –interleave=all啓動mysqld進程的方式NUMA不一樣Node間分配的內存會更加均衡。 這個差別是與innodb_numa_interleave參數執行的策略有關,開啓後,全局內存採用了interleave的分配方式,但線程內存採用了default的本地分配方式。 而若是使用numactl –interleave=all啓動mysqld進程,全部內存都會採用interleave的分配方式。