香港服务器Swap配置与OOM处理:内存不足时的应急方案与长期优化策略
香港 VPS 内存用尽时,Linux 内核会触发 OOM Killer(内存耗尽杀手),随机终止某个进程来释放内存——通常杀掉的正好是你的数据库或 Web 服务器,导致业务中断。Swap 分区是防止 OOM 的重要缓冲,但配置不当(如 Swappiness 过高)会导致 SSD 过度写入和性能下降。本文提供从应急处置到根本优化的完整方案。
一、理解 OOM 和 Swap 的关系
<code">
内存压力升级过程:
物理 RAM 使用率 < 70% → 正常运行,Swap 不使用 物理 RAM 使用率 70~90% → 内核开始将不活跃的内存页换出到 Swap 物理 RAM 使用率 > 90%(+ Swap 也快满)
→ OOM Killer 激活,强制杀死某个进程
全部耗尽
→ 系统崩溃或卡死
Swap 的作用:
- 为偶发内存峰值提供缓冲
- 允许将长时间不活跃的数据移到磁盘
- 不是让你持续超量使用内存的方案
二、创建 Swap 文件(SSD 优化方案)
<code"># 推荐:使用 Swap 文件(比 Swap 分区更灵活,可随时调整大小) # Swap 大小建议: # RAM < 2GB:Swap = 2 × RAM # RAM 2~8GB:Swap = 等于 RAM # RAM > 8GB:Swap = 4~8GB(超大 Swap 意义不大) # 示例:为 4G 内存服务器创建 4GB Swap fallocate -l 4G /swapfile # 快速分配(比 dd 快) chmod 600 /swapfile # 安全权限(只有 root 可读) mkswap /swapfile # 格式化为 Swap swapon /swapfile # 立即启用 # 验证 free -h swapon --show # 永久生效(重启后依然启用) echo '/swapfile none swap sw 0 0' >> /etc/fstab
三、Swappiness 调优(关键参数)
<code"># Swappiness 范围 0~100 # 0 = 尽量不使用 Swap(只在物理 RAM 即将耗尽时使用) # 60 = 默认值(较积极地换出,不适合 SSD 服务器) # 100= 积极换出(不推荐) # 查看当前值 cat /proc/sys/vm/swappiness # 临时修改(立即生效,重启失效) sysctl vm.swappiness=10 # 永久修改 echo 'vm.swappiness=10' >> /etc/sysctl.conf sysctl -p # 推荐值: # SSD 服务器:10(减少 SSD 写入,物理 RAM 尽量用满) # HDD 服务器:30~40(磁盘速度慢,适当早换出) # 数据库服务器:1(数据库自己管理内存缓存,减少内核干预)
<code"># VFS Cache Pressure:控制内核回收文件系统缓存的积极程度 # 默认 100,降低后内核更倾向保留文件缓存(对磁盘 I/O 密集应用有利) echo 'vm.vfs_cache_pressure=50' >> /etc/sysctl.conf # 脏页比例:控制何时将内存中的脏数据写入磁盘 echo 'vm.dirty_ratio=15' >> /etc/sysctl.conf echo 'vm.dirty_background_ratio=5' >> /etc/sysctl.conf sysctl -p
四、OOM Killer 策略配置
<code"># OOM Killer 通过 oom_score 决定先杀哪个进程 # oom_score 范围 -1000 到 1000,分数越高越容易被杀 # 查看各进程的 OOM 分数 cat /proc/$(pgrep nginx | head -1)/oom_score cat /proc/$(pgrep mysql | head -1)/oom_score # 保护关键进程(降低被杀的概率) # oom_score_adj 范围 -1000 到 1000 # -1000 = 永远不被 OOM Killer 杀死(最保护) # 0 = 默认 # 1000 = 最优先被杀死 # 保护 MySQL(永远不被 OOM 杀死) echo -1000 > /proc/$(pgrep mysqld)/oom_score_adj # 保护 Nginx 主进程 echo -500 > /proc/$(pgrep -x nginx | head -1)/oom_score_adj # 让不重要的进程优先被杀(如测试进程) echo 500 > /proc/$(pgrep python3)/oom_score_adj
<code"># 通过 systemd 永久设置 OOM 保护 # /etc/systemd/system/mysql.service.d/oom.conf [Service] OOMScoreAdjust=-900 # MySQL 被杀的概率极低 # /etc/systemd/system/nginx.service.d/oom.conf [Service] OOMScoreAdjust=-500 # 使用 /etc/security/limits.conf 也可以(对非 systemd 管理的进程) systemctl daemon-reload
五、内存使用实时监控
<code"># 内存占用排行榜(按实际占用排序)
ps aux --sort=-%mem | head -20
# 更精确的内存统计(区分 RSS 和 PSS)
cat /proc/$(pgrep mysqld | head -1)/smaps_rollup | grep -E "Rss|Pss|Swap"
# 实时内存监控脚本
cat > /usr/local/bin/mem-watch.sh << 'EOF'
#!/bin/bash
while true; do
clear
echo "=== 内存监控 $(date) ==="
free -h
echo ""
echo "=== Top 10 内存进程 ==="
ps aux --sort=-%mem | head -11 | awk '{printf "%-6s %-5s %-5s %s\n", $2, $3, $4, $11}'
echo ""
echo "=== Swap 使用详情 ==="
swapon --show
sleep 5
done
EOF
chmod +x /usr/local/bin/mem-watch.sh六、OOM 事件检测与告警
<code"># 检查是否发生过 OOM 事件
dmesg | grep -i "oom\|killed process" | tail -20
# 或者
journalctl -k | grep -i "oom killer" | tail -20
# OOM 事件告警脚本(检测到 OOM 立即通知)
cat > /usr/local/bin/oom-monitor.sh << 'EOF' #!/bin/bash FEISHU="https://open.feishu.cn/open-apis/bot/v2/hook/your_webhook" LAST_CHECK_FILE="/tmp/oom-last-check" LAST_CHECK=$(cat "$LAST_CHECK_FILE" 2>/dev/null || echo "0")
# 检查从上次检查到现在是否有新的 OOM 事件
OOM_EVENTS=$(journalctl -k --since "@$LAST_CHECK" 2>/dev/null | grep -c "Killed process" || echo "0")
if [ "$OOM_EVENTS" -gt 0 ]; then
OOM_DETAIL=$(journalctl -k --since "@$LAST_CHECK" | grep "Killed process" | tail -3)
curl -s -X POST "$FEISHU" \
-H "Content-Type: application/json" \
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"🚨 OOM Killer触发!\n服务器:$(hostname)\n事件数:$OOM_EVENTS\n详情:$OOM_DETAIL\n当前内存:$(free -h | grep Mem)\"}}"
fi
date +%s > "$LAST_CHECK_FILE"
EOF
chmod +x /usr/local/bin/oom-monitor.sh
echo "*/5 * * * * root /usr/local/bin/oom-monitor.sh" >> /etc/crontab七、进程内存优化(从根本解决)
<code"># MySQL 内存优化 # /etc/mysql/mysql.conf.d/memory.cnf [mysqld] # 将 innodb_buffer_pool_size 设为可用内存的 60~70% # 4G 服务器:约 2.5G innodb_buffer_pool_size = 2500M innodb_buffer_pool_instances = 2 # 每个实例 1.25G # 减少连接内存(每连接占 1~10MB) max_connections = 100 # 按实际需要设置,不要太大 # PHP-FPM 内存优化 # /etc/php/8.1/fpm/pool.d/www.conf pm.max_children = 20 # 根据内存计算:(总内存-500MB) / 每进程内存 php_admin_value[memory_limit] = 128M # PHP 脚本内存上限
<code"># 查找内存泄漏进程(内存持续增长)
# 记录各进程内存,对比1小时前后的变化
cat > /tmp/mem-snapshot.sh << 'EOF' ps aux --sort=-%mem | awk 'NR>1{print $4, $2, $11}' | head -20 > /tmp/mem-$(date +%H%M).txt
EOF
# 每10分钟记录一次
echo "*/10 * * * * /tmp/mem-snapshot.sh" | crontab -
# 分析哪个进程内存增长最快
diff /tmp/mem-0900.txt /tmp/mem-1000.txt八、总结
Swap 是内存不足时的安全气囊,而不是常规行驶的燃料——如果 Swap 持续被大量使用,说明物理内存严重不足,应当升级服务器配置而非无限增大 Swap。正确的处置顺序:配置 Swap 应急缓冲 → 调整 Swappiness 减少 SSD 磨损 → 为关键进程设置 OOM 保护 → 排查根本的内存泄漏或配置问题 → 必要时升级服务器内存。