本文约 3,714 字,预计阅读 10 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:内存一满,站点就 502
低配云服务器(1 核 2G、2 核 2G)跑 WordPress 的常见症状:
- 平时正常,备份、更新、批量操作时站点卡死;
- MySQL 进程被杀,站点报”数据库连接失败”;
- 日志里全是
Out of memory: Kill process; - 装了 Redis + MySQL + PHP-FPM + Nginx,内存刚好卡在临界点。
绝大多数云主机默认没有 swap(阿里云、腾讯云、AWS 的镜像都默认关闭)。这意味着内存一满,内核直接杀进程,没有任何缓冲。加一个 swap 文件,相当于给系统留了一条”慢但不会死”的退路。
原理分析:swap 是什么,代价是什么
swap 是磁盘上的一块空间,当物理内存紧张时,内核把不常用的内存页换出到磁盘,腾出物理内存给活跃进程。
好处:
- 内存峰值时有缓冲,不会立刻 OOM;
- 冷数据(比如长时间不用的 PHP-FPM 空闲进程)被换出,热数据留在内存。
代价:
- 磁盘比内存慢几个数量级。一旦开始大量交换,系统会变得极慢(俗称”swap 抖动”);
- SSD 有写入寿命,频繁 swap 会消耗(但对现代 SSD 影响很小,不必过度担心)。
关键认识:swap 是保险丝,不是扩容。它的价值在于”偶尔用到时不会死”,而不是”让你放心超售内存”。如果 si/so(换入换出)持续很高,说明内存真的不够,该升级配置了。
swappiness 参数控制内核使用 swap 的倾向:
0:尽可能不用 swap(在内存回收时才用,Linux 3.5+ 的语义);1:最低限度使用;60:默认值,比较积极;100:非常积极。
对服务器,推荐 10–30:既保留兜底能力,又不会在日常运行时把热数据换出去。
保姆级实操
第一步:查看当前 swap 情况
free -h
swapon --show
cat /proc/sys/vm/swappiness
如果 Swap: 0B,说明没开。
第二步:创建 swap 文件
推荐大小:
| 物理内存 | swap 建议 |
|---|---|
| ≤ 2GB | 内存的 2 倍 |
| 2–8GB | 等于内存大小 |
| 8–64GB | 8GB |
| > 64GB | 4–8GB(或不用) |
# 创建 2GB 的 swap 文件(fallocate 最快)
sudo fallocate -l 2G /swapfile
# 如果 fallocate 报错(某些文件系统不支持),用 dd
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress
# 权限必须 600,否则 swapon 拒绝
sudo chmod 600 /swapfile
# 格式化
sudo mkswap /swapfile
# 启用
sudo swapon /swapfile
验证:
free -h
# Swap: 2.0Gi ...
第三步:设置开机自动挂载
# 备份 fstab
sudo cp /etc/fstab /etc/fstab.bak
# 追加一行
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# 检查语法(无输出即正常)
sudo mount -a
第四步:调整 swappiness
# 临时生效
sudo sysctl vm.swappiness=20
# 永久生效
echo 'vm.swappiness=20' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
另外一个有用的参数(减少内核缓存对 swap 的干扰):
# 控制"内核为 page cache 回收多少 inode/dentry 缓存",默认 100
echo 'vm.vfs_cache_pressure=50' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
第五步:WordPress 侧同步调整内存限制
swap 只是兜底,WordPress 本身也要合理设限。在 wp-config.php 里:
// 前台不需要太大,后台才需要
define( 'WP_MEMORY_LIMIT', '128M' );
define( 'WP_MAX_MEMORY_LIMIT', '256M' );
同时 PHP-FPM 的 memory_limit 要匹配(不能低于 WordPress 的设置):
; /etc/php/8.2/fpm/php.ini
memory_limit = 256M
注意:memory_limit 是所有 PHP-FPM 进程各自的限制,不是总量。10 个进程 × 256M 理论峰值就是 2.56GB。这也是 PHP-FPM 进程数计算 里要用真实 RSS 而非 memory_limit 的原因。
第六步:监控 swap 使用情况
# 实时看换入换出(si/so 列,单位 KB/s)
vmstat 1
# 看哪些进程用了 swap
for f in /proc/[0-9]*/status; do
pid=$(basename $(dirname $f))
swap=$(awk '/VmSwap/{print $2}' $f 2>/dev/null)
[ -n "$swap" ] && [ "$swap" -gt 0 ] && \
echo "$pid $(awk '/^Name/{print $2}' $f) ${swap}kB"
done | sort -k3 -rn | head -10
判断标准:
si/so长期为 0 → swap 只是保险,配置合理;si/so偶尔非零 → 峰值时有缓冲,这正是我们想要的;si/so持续几百 KB/s 以上 → 内存真的不够,该升级配置。
常见踩坑
fallocate创建的 swap 文件在某些文件系统上不可用(如某些版本的 btrfs、OpenVZ 的老内核)。用dd更保险,只是慢一点。- 权限没设 600:
swapon报insecure permissions 0644, fix with: chmod 600。 - 云服务器用云盘:swap 在云盘上 I/O 更慢,抖动时更卡。可以考虑用 zRAM(内存内压缩 swap),比磁盘 swap 快很多:
bash
sudo apt install zram-tools -y
sudo systemctl enable --now zramswap
zRAM 把内存压缩后当 swap 用,相当于”用 CPU 换内存”,在小内存机器上效果比磁盘 swap 好得多。
- 案例:某 1G 内存站加了 2G swap,结果站点反而更慢了——因为
swappiness保持默认 60,MySQL 的innodb_buffer_pool被换出到磁盘,每次查询都走磁盘 IO。把swappiness改成 10 并把buffer_pool从 512M 降到 256M 后恢复正常。swap 不能替代正确的内存规划。 - 容器环境:Docker/K8s 里加 swap 要宿主机支持,且 K8s 默认禁用 swap。容器内改不了,需要在宿主机配。
- 误以为 swap 能解决一切:如果
dmesg里持续 OOM,说明峰值需求远超物理内存,swap 只是延后死亡。真正的解法是减少内存占用——查哪个插件吃内存(用 Query Monitor 看 Memory Usage),或者上 Redis 减少重复加载。 - 删除 swap(如果后续升级了内存):
bash
sudo swapoff /swapfile
sudo rm /swapfile
# 记得把 /etc/fstab 里那行删掉
总结
swap 的正确定位:防止偶发峰值导致 OOM 的保险,不是性能方案。配置四步:fallocate 建文件 → chmod 600 → mkswap + swapon → 写 fstab 持久化;再把 vm.swappiness 调到 10–30,避免日常运行时把热数据换出去。判断配置是否健康看 vmstat 的 si/so:长期为 0 最好,偶尔非零正常,持续很高就必须加内存或减占用。同时别忘了检查 WordPress 的 WP_MEMORY_LIMIT 和 PHP 的 memory_limit 是否设得过大(虚高限制会让 OOM 来得更早)。关于内存占用的进一步优化,可参考 PHP-FPM 进程数计算 和 WordPress Redis 对象缓存。
