一、先决条件
-
MySQL 5.7.30
-
vmware 虚拟机 CentOS 7.5
-
CPU 4C
-
内存 4G
-
innodb_buffer_pool_size = 3G
二、环境复现
2.1 开启大页
- 如果存在 echo 开启大页页数不准,请再次执行 echo 语句即可
# 设置大页内存,每一页的大小( 默认:2M ) 当前 2097152 就是 2M
[root@db ~]# echo 2097152 > /proc/sys/kernel/shmall
[root@db ~]# cat /proc/sys/kernel/shmall
2097152
# 设置使用的页数,每个页如果是 2MB ,所以总和就为 2000 MB.
[root@db ~]# echo 1000 > /proc/sys/vm/nr_hugepages
[root@db ~]# cat /proc/meminfo | grep -i huge
AnonHugePages: 4096 kB
HugePages_Total: 1000
HugePages_Free: 1000
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
2.2 授权用户
- 设置访问大页内存的操作系统的
用户组
ID ,mysql 用户必须在这个组内
[root@db ~]# id mysql
uid=1000(mysql) gid=1000(mysql) groups=1000(mysql)
[root@db ~]# echo 1000 > /proc/sys/vm/hugetlb\_shm\_group
[root@db ~]# cat /proc/sys/vm/hugetlb\_shm\_group
1000
2.3 查看内存
- 此时大页内存已经占用了 2G 的内存空间,同时 MySQL 实例也分配了 2G Buffer 空间
[root@db ~]# free -h
total used free shared buff/cache available
Mem: 3.7G 2.6G 1.0G 16M 116M 981M
Swap: 0B 0B 0B
三、故障复现
- 分配了 3G Buffer 给 MySQL,不过不是立刻占用的,需要MySQL一点点使用才分配
3.1 模拟业务
- 当前环境存在 4 张 200 万数据的表
[root@db ~]# sysbench /usr/share/sysbench/tests/include/oltp_legacy/oltp.lua --mysql-host=10.186.60.60 --mysql-port=3306 --mysql-user=root --mysql-password=123456 \
--mysql-db=testdb --oltp-table-size=2000000 --oltp-tables-count=10 --threads=4 --events=0 --time=3600 --report-interval=3 run
3.2 数据库OOM
-
服务器发生了 OOM,数据库宕机
-
计算 rss 的数量( 此处为页数 ),每页为 4K,计算完成近大约为 1.6G。
-
那么问题来了,
- 主机内存 4G,实际才使用了 1.6G 多,怎么会发现生 OOM,free used 使用了 3.6G,那么我的内存去哪了?
3.3 大页内存
- 在这里,传统大页 total 配置了 1000,free 也为 1000,说明配置了大页但没在使用,hugepagesize 为 2M,这一块预留的就是 2G 大页内存。
[root@db ~]# cat /proc/meminfo | grep -i huge
AnonHugePages: 4096 kB
HugePages_Total: 1000
HugePages_Free: 1000
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
“大内存页” 也称传统大页、大页内存等有助于 Linux 进行虚拟内存的管理,标准的内存页为 4KB,这里使用“大内存页”最大可以定义 1GB 的页面大小,在系统启动期间可以使用“大内存页”为应用程序预留一部分内存,这部分内存被占用且永远不会被交换出内存,它会一直保留在那里,直到改变配置。
3.4 大页内存分配谁?
- hugetlb_shm_group 文件里填的是指定大页内存使用的
用户组
id,这里查看到是 MySQL 组 id,那既然是给 MySQL 的为什么 free 等于 total,并且 mysql 还只有 1.6G 实际使用内存呢?- 原来在 MySQL 中还有专门启用大内存页的参数,在 MySQL 大内存页称为
large_page
- https://dev.mysql.com/doc/refman/5.7/en/large-page-support.html
- 原来在 MySQL 中还有专门启用大内存页的参数,在 MySQL 大内存页称为
[root@db ~]# cat /proc/sys/vm/hugetlb_shm_group
1000
[root@db ~]# id 1000
uid=1000(mysql) gid=1000(mysql) groups=1000(mysql)
后与业务确认,很早之前确实启用过 mysql 的 large page,不过后面禁用了。排查到这基本就有了结论。
3.5 结论
-
这套环境之前开启了 1000 的大内存页,每页大小为 2MB,占用了 2G 内存空间,给 MySQL 使用,并且 MySQL 开启了 large page,但后来不使用的时候,只关闭了 MySQL 端的 large page 参数,但没有实际更改主机的关于大内存页的配置,所以导致,实际上主机上的还存在 1000 的大内存页,并且没在使用,这一部分长期空闲,并且其他程序不能使用。
-
所以 MySQL 在使用 1.6G 内存左右,整个主机内存就饱和了,然后在部分条件下,就触发了 OOM,导致 mysqld 被 kill,但主机上又有 mysqld_safe 守护程序,所以又再次给拉起来,就看到了文章初的偶尔连接不上的现象。
解决:
经过在本地测试,确实指定大页之后会导致内存占用,如果 MySQL 不配置,会空闲这部分内存,且模拟大业务的情况下会发生 OOM。所以,在问题环境上:
通过移除 vm 相关参数,使被占用的大内存页释放出来,MySQL 就没再被 oom 过。
注意:/etc/rc.local 、/etc/sysctl.conf 两个文件检查下,避免服务器重启后再次启动 大页内存
[root@db ~]# echo 0 > /proc/sys/vm/nr_hugepages
建议:
1、MySQL 主机一般不必使用 hugepage,MySQL 自己处理 buffer pool 的分页管理,不需要操作系统的参与;
2、建议在环境上线前,检查一下主机内存的大内存页分配,看是否有没在使用的传统大页的存在,避免影响后续业务的使用。
大页内存参考链接:https://kerneltalks.com/services/what-is-huge-pages-in-linux/
- 其他玩意⬇️
[root@db ~]# yum -y install numactl
[root@db ~]# numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1 2 3
node 0 size: 4095 MB
node 0 free: 803 MB
node distances:
node 0
0: 10