MySQL内存不足故障分析与解决
一、故障现象:内存占用异常的服务器
某天,运维人员发现一台数据库服务器发出内存不足告警,监控显示空闲内存已低于 10%。通过
top命令查看系统资源使用情况,输出如下:top - 13:45:43 up 1835 days, 20:52, 2 users, load average: 0.02, 0.03, 0.05
Tasks: 210 total, 1 running, 208 sleeping, 1 stopped, 0 zombie
%Cpu(s): 0.5 us, 0.6 sy, 0.0 ni, 98.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 32780028 total, 905684 free, 19957900 used, 11916444 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 3448260 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2677 mysql 20 0 20.1g 15.1g 3392 S 0.0 48.2 430:17.58 mysqld
10549 polkitd 20 0 3277476 3.1g 632 S 0.3 9.9 146:47.24 redis-server
18183 root 20 0 877308 215868 1892 T 2.7 0.7 2736:45 xxxxxx
442 root 20 0 160244 93016 88552 S 0.3 0.3 314:14.86 systemd-journal
32537 root 20 0 731620 58360 54588 S 0.3 0.2 29:09.61 rsyslogd
系统内存总量为 32GB,已使用 19GB,缓存和缓冲区占用 11GB,但可用内存仅剩约 3GB。进一步分析发现,占用内存最多的是 MySQL 和 Redis 进程,合计约 18.2GB,其他进程内存占用都比较低。那么问题来了:缓存和缓冲区的 11GB 内存中,为什么只有 3GB 是有效的,剩下的 8GB 去哪儿了?
二、深入分析:被隐藏文件占用的内存
1. 内存使用详情分析
执行
free -m命令进一步查看内存使用情况: total used free shared buff/cache available
Mem: 32011 19490 881 8762 11639 3366
Swap: 0 0 0
结果显示
shared内存占用竟然高达 8GB。通过man命令查看帮助信息得知,shared内存来源于/proc/meminfo中的Shmem,主要由tmpfs使用。2. tmpfs 内存占用定位
使用
df -h命令查看文件系统使用情况:Filesystem Size Used Avail Use% Mounted on
devtmpfs 16G 0 16G 0% dev
tmpfs 16G 16K 16G 1% dev/shm
tmpfs 16G 8.6G 7.1G 55% run
tmpfs 16G 0 16G 0% sys/fs/cgroup
发现
/run目录占用了 8.6GB 内存,这与shared内存占用量一致。接下来分析/run目录下的内存占用情况:du -am run|sort -rn -k1|head -10
8761 run
7137 run/systemd
7126 run/systemd/users
1624 run/log/journal/89308070e0c04c6a86bf577f4064efca
1624 run/log/journal
1624 run/log
内存主要消耗在
/run/systemd/users和/run/log/journal目录,分别占用 7126MB 和 1624MB。其中/run/systemd/users目录的内存占用异常高,进一步查看该目录下的文件:ls -l run/systemd/users
total 44
-rw-r--r-- 1 root root 41056 Mar 23 14:14 0
乍一看,该目录下只有一个约 40KB 的文件,这与
du统计的 7GB 差异巨大。难道有隐藏文件?使用find命令查找隐藏文件:find run/systemd/users|less
/run/systemd/users/
/run/systmd/users/0/
/run/systemd/users/.#0kRUlqC/
/run/systemd/users/.#0Qxvu5J/
/run/systemd/users/.#03DvfrF/
......
find run/systemd/users|wc -l
337632
不查不知道,一查吓一跳!该目录下隐藏文件数量高达 30 多万个,最早的文件可以追溯到 2018 年,最新的文件则是当天产生的。随便打开一个隐藏文件查看内容:
less run/systemd/users/.#03DvfrF
# This is private data. Do not parse.
NAME=root
STATE=active
RUNTIME=/run/user/0
SLICE=user-0.slice
DISPLAY=4231719
REALTIME=1521010223727718
MONOTONIC=79029110787
SESSIONS=4232100 4232099 4232098 ......
文件内容显示这些是 root 用户的 session 信息。使用
loginctl命令查看 session 信息:loginctl list-sessions
SESSION UID USER SEAT
24597 0 root
146401 0 root
133160 0 root
82494 0 root
82514 0 root
106049 0 root
......
loginctl list-sessions|awk '{print $3}'|sort|uniq -c
1 1 listed.
2131 root
1 USER
结果显示 root 用户的 session 数竟然高达 2131 个!随便查看一个 session 的状态:
loginctl session-status 24597
24597 - root (0)
Since: Tue 2018-03-27 08:35:01 CST; 4 years 11 months ago
Leader: 25599
Service: crond; type unspecified; class background
State: active
Unit: session-24597.scope
这些 session 大多是由
crond产生的,且都处于active状态,但当前并没有分配相关进程。更令人惊讶的是,按 session 创建时间排序后发现,最近的 session 也是 2018 年产生的,也就是说这些 session 已经存在了将近 5 年!三、问题根源:crond 产生的僵尸 session
进一步测试发现,当创建一个新的定时任务时,session 能正常分配进程,任务完成后 session 也能正常关闭。但不知为何,过去几年中产生的大量 crond session 没有被正确关闭,一直处于
active状态,导致相关的临时文件保留在/run/systemd/users目录下,占用了大量内存。通过
lsof命令查看与这些 session 相关的文件描述符:lsof -p `pidof dbus-daemon`|grep sessions|wc -l
2126
lsof -p `pidof dbus-daemon`|tail -5
dbus-daem 560 dbus 2139w FIFO 0,18 0t0 416861417 /run/systemd/sessions/156582.ref
dbus-daem 560 dbus 2140w FIFO 0,18 0t0 417383549 /run/systemd/sessions/156774.ref
dbus-daem 560 dbus 2141w FIFO 0,18 0t0 417291412 /run/systemd/sessions/156740.ref
dbus-daem 560 dbus 2142w FIFO 0,18 0t0 620267085 /run/systemd/sessions/242902.ref
dbus-daem 560 dbus 2143w FIFO 0,18 0t0 621086290 /run/systemd/sessions/243335.ref
可以看到,
dbus-daemon进程打开了大量与 session 相关的文件描述符,这些文件都是保存在tmpfs中的,因此会占用内存。四、解决方案:释放内存的多种选择
方案 1:调整数据库内存配置(在线处理)
考虑到服务器上主要服务为 MySQL 和 Redis,其中 MySQL 作为从库使用,未承载业务读流量,Redis 近期也将迁移。虽然
/run/systemd/users目录占用的内存还在缓慢增长,但 5 年才占用了 8GB,增长速度较慢。因此可以采取以下措施:- 在线收缩 MySQL 的
innodb_buffer_pool_size参数,释放一部分内存给操作系统。 - 等待 Redis 迁移完成后,重启服务器彻底释放内存。
方案 2:手动清理僵尸 session(不重启服务器)
如果服务器不允许重启,可以采取以下步骤清理僵尸 session:
- 通过
lsof命令确认这些隐藏文件当前未被使用。 - 将
/run/systemd/users目录下的文件迁移到其他磁盘目录,尝试释放内存。 - 使用
loginctl kill-session ID命令强制关闭这些僵尸 session。
五、最终处理:选择最稳妥的方案
综合考虑各种因素,运维人员最终选择了方案 1 进行处理:
- 调整 MySQL 的
innodb_buffer_pool_size参数,释放部分内存。 - 制定计划,在 Redis 迁移完成后重启服务器,彻底解决内存占用问题。
六、经验总结:系统维护中的细节不容忽视
- 定期检查系统内存使用情况:不要等到告警产生后才发现问题,应定期通过
top、free、df等命令检查系统内存使用情况。 - 关注 tmpfs 文件系统:
tmpfs使用内存作为存储,当发现shared内存占用异常时,应检查/run、/dev/shm等tmpfs挂载点。 - 及时清理僵尸进程和 session:定期检查系统中的僵尸进程和过期 session,避免资源浪费。
- 合理配置定时任务:确保定时任务执行完毕后,相关的 session 和进程能正确关闭。
- 制定合理的维护计划:对于长期运行的服务器,应制定定期重启计划,释放长期积累的资源占用。
通过这个案例,我们可以看到,即使是看似简单的内存不足问题,背后也可能隐藏着复杂的原因。在系统维护过程中,只有深入分析、步步排查,才能找到问题的根源并采取有效的解决方案。
浙公网安备 33010602011771号