1. 为什么你的Linux系统突然变慢了?
最近有台服务器跑得特别慢,连最简单的命令都要等好几秒才能响应。我登录上去一看,好家伙,物理内存早就被吃光了,swap分区占用率高达90%!这种情况在很多Linux服务器上都很常见,特别是那些内存配置不太充裕的机器。今天我就来分享一套完整的swap问题排查和优化方案,都是我这些年踩坑总结出来的实战经验。
swap分区相当于Windows系统的虚拟内存,当物理内存不够用时,系统会把暂时用不到的内存数据"倒腾"到swap空间里。但问题在于,硬盘速度比内存慢得多,一旦系统开始频繁使用swap(我们称之为swap thrashing),性能就会断崖式下跌。我见过最夸张的情况是,某个Java应用因为内存泄漏导致swap被撑爆,整个系统完全卡死,最后只能硬重启。
2. 快速诊断swap使用情况
2.1 查看swap基础信息
首先我们得知道系统到底有没有在用swap。最直观的命令是free -h,这个-h参数表示用人类可读的单位显示(比如GB/MB):
$ free -h total used free shared buff/cache available Mem: 7.7G 5.2G 200M 20M 2.3G 2.1G Swap: 2.0G 1.8G 200M这里可以看到我的swap总共2GB,已经用了1.8GB,情况确实不太妙。如果想看更详细的信息,可以用cat /proc/swaps:
$ cat /proc/swaps Filename Type Size Used Priority /swapfile file 2097148 1903228 -2这个输出告诉我们swap存储在哪里(/swapfile),以及具体的占用情况。很多新手会困惑为什么有些系统用独立分区(/dev/sdaX),有些用文件(/swapfile)。其实两种方式性能差不多,但文件形式更灵活,后面我们会讲到如何转换。
2.2 找出谁在吃swap
知道swap被占用了还不够,我们得找出罪魁祸首。这里推荐用smem工具,它能按进程统计内存使用情况:
$ sudo smem -t -s swap PID User Command Swap USS PSS RSS 1234 mysql /usr/sbin/mysqld 1.2G 500M 600M 800M 5678 java /usr/bin/java -Xmx4g 800M 300M 400M 500M从输出可以看到,MySQL和Java进程占用了大量swap空间。特别是Java应用,如果配置了过大的堆内存(-Xmx),很容易导致swap滥用。这时候就该考虑调整JVM参数或者给服务器加内存了。
3. 紧急处理:当swap快被撑爆时
3.1 临时释放swap空间
如果swap使用率已经超过90%,系统响应明显变慢,我们可以先紧急释放swap:
# 先同步内存数据到磁盘 $ sudo sync # 关闭所有swap $ sudo swapoff -a # 重新启用swap $ sudo swapon -a这个操作相当于给swap做了一次"重启",但要注意:
- 执行期间系统会变得非常卡,因为所有swap数据都要读回内存
- 如果物理内存本身就不够,这个操作可能导致OOM(内存溢出)错误
- 这只是临时措施,系统运行一段时间后swap又会慢慢被占满
3.2 调整swappiness参数
Linux有个内核参数叫vm.swappiness,值从0到100,表示系统有多"积极"使用swap。默认值通常是60,对于数据库服务器来说太高了。我们可以临时调整为10:
$ echo 10 | sudo tee /proc/sys/vm/swappiness要让设置永久生效,需要修改/etc/sysctl.conf文件:
$ echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf $ sudo sysctl -p这个值不是越小越好。设为0表示"除非万不得已,否则不用swap",但在内存压力很大时可能导致进程直接被OOM killer杀掉。建议生产环境设置在10-30之间。
4. 长期优化方案
4.1 合理规划swap大小
传统经验说swap应该是物理内存的2倍,但这个规则早就过时了。现代服务器的内存配置动辄几十GB,完全按这个比例分配swap既不现实也没必要。我的建议是:
- 内存≤4GB:swap=内存×2
- 4GB<内存≤16GB:swap=内存大小
- 内存>16GB:swap=16GB
对于云服务器,如果本身配置了充足的内存(比如32GB以上),甚至可以完全不用swap。但像MySQL这类数据库服务,保留一定swap空间还是有好处的。
4.2 使用swap文件替代分区
传统方式是划分独立分区作为swap,但现在更推荐使用swap文件,管理起来灵活得多。下面是创建4GB swap文件的步骤:
# 创建空文件(注意fallocate在某些文件系统有问题,推荐用dd) $ sudo dd if=/dev/zero of=/swapfile bs=1M count=4096 # 设置权限 $ sudo chmod 600 /swapfile # 格式化为swap $ sudo mkswap /swapfile # 启用swap文件 $ sudo swapon /swapfile要让系统启动时自动挂载,需要在/etc/fstab添加:
/swapfile none swap sw 0 0如果想删除旧的swap分区,记得先在fstab里注释掉对应的行,然后执行swapoff /dev/sdXN(把XN换成实际分区)。
4.3 监控与告警设置
预防胜于治疗,建议设置swap使用率监控。用Zabbix等监控工具当然好,但简单的shell脚本也能搞定:
#!/bin/bash SWAP_THRESHOLD=80 CURRENT_USAGE=$(free | awk '/Swap/{printf "%.0f", $3/$2*100}') if [ "$CURRENT_USAGE" -gt "$SWAP_THRESHOLD" ]; then echo "Warning: Swap usage is ${CURRENT_USAGE}%" | mail -s "High Swap Alert" admin@example.com fi把这个脚本加到crontab里每小时跑一次,就能在swap使用率超过80%时收到邮件告警。
5. 特殊场景处理
5.1 数据库服务器的swap优化
MySQL、PostgreSQL这类数据库对内存非常敏感。除了调整swappiness,还有几个关键参数:
# InnoDB缓冲池大小(建议是物理内存的50-70%) innodb_buffer_pool_size = 12G # 让InnoDB尽量使用O_DIRECT方式访问磁盘 innodb_flush_method = O_DIRECT # 减少内存排序使用临时表 tmp_table_size = 64M max_heap_table_size = 64M对于Redis这种纯内存数据库,建议直接禁用swap:
$ sudo swapoff -a $ sudo sysctl vm.overcommit_memory=15.2 容器环境下的swap问题
Kubernetes默认是禁用swap的,但有些特殊场景可能需要开启。如果要在Docker中使用swap,需要注意:
- 容器内存限制(-m)要设置合理
- 避免swap导致的内存竞争影响宿主机
- 考虑使用memory.swappiness=0的cgroup参数
在k8s中,可以通过kubelet参数--fail-swap-on=false来允许节点使用swap,但这通常不是最佳实践。
6. 性能对比测试
为了验证不同配置的效果,我在一台8GB内存的测试机上做了基准测试:
| 配置方案 | 内存压力测试得分 | MySQL查询吞吐量 | 系统响应延迟 |
|---|---|---|---|
| 默认swap(2GB) | 75 | 1200 QPS | 1.2s |
| 无swap | 68 | 1500 QPS | 0.8s |
| swap文件(4GB)+优化参数 | 82 | 1800 QPS | 0.5s |
测试结果显示,完全禁用swap在某些场景下确实能提升性能,但系统稳定性会下降。经过优化的swap配置(适当大小+调整参数)能在性能和稳定性间取得平衡。