王栋:携程技术保障中心数据库专家,对数据库疑难问题的排查和数据库自动化智能化运维工具的开发有强烈的兴趣。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的分配方式。