香港服务器Swap配置与OOM处理:内存不足时的应急方案与长期优化策略

香港服务器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 保护 → 排查根本的内存泄漏或配置问题 → 必要时升级服务器内存。

Telegram