加载中...
服务器内存耗尽宕机与资源调优分析
发表于:2026-10-21 | 分类: resultweb
字数统计: 1.9k | 阅读时长: 7分钟 | 阅读量:

服务器内存耗尽宕机与资源调优分析

一、事件概述

前端服务器(CentOS 7 虚拟机,48GB 内存 / 24 vCPU / 5GB Swap,运行 Rails + Passenger + MySQL + NFS 挂载)发生整机卡死,SSH 无法连接、服务停止响应,最终通过硬重启恢复。排查后确认是内存耗尽(OOM)引发的连锁故障。

二、故障时间线

时间 事件
T0 起 一个日志归档服务进入崩溃重启循环(约 25 秒一次),持续 1.5 小时以上
T0+1.5h 内核出现 page allocation failure,内存耗尽
随后 NFS 客户端报 “server not responding”——连网卡收包都分配不到内存
随后 OOM Killer 触发,Free swap = 0,48GB 内存 + 5GB swap 全部耗尽
随后 OOM 连续杀多个进程仍无法恢复,系统彻底卡死(日志中断约 14 小时)
次日 硬重启后恢复

三、根本原因

多因素叠加导致的内存耗尽:

1. Passenger Worker 过多(主因)

OOM 时进程列表显示 200+ 个 Ruby worker 进程,每个占用 60160MB,合计 3040GB。配置为固定常驻 110 个 worker(PassengerMaxPoolSize 110 + PassengerMinInstances 110),每个约 310MB,是 48GB 机器上最大的内存消耗来源。

2. 日志归档服务崩溃循环(加压因素)

该服务每 25 秒重启一次,每次都加载完整 Rails 环境(约 100-200MB),持续给已紧张的内存施压。

3. Swap 不足

5GB swap 对 48GB 内存比例过小,没有缓冲空间,很快用尽。

4. NFS 连锁卡死

内存耗尽后,虚拟网卡驱动无法分配页面接收网络包 → NFS 客户端无法完成写回 → 访问 NFS 挂载点的进程进入不可中断(D)状态 → SSH 也被拖住 → 整机卡死。

5. 内核保留内存过低(关键放大因素)

vm.min_free_kbytes 默认仅 66MB。这个参数决定内核为网卡收包、中断处理、磁盘 I/O 等”原子分配”保留的最低内存。66MB 在 48GB 机器上太小,当用户进程把内存吃到只剩几十 MB 时,内核关键路径(网络、NFS)先于 OOM Killer 崩溃,导致网络瘫痪。

四、恢复措施

  1. 硬重启 NFS 服务器(先恢复存储)
  2. 硬重启前端服务器
  3. 清理残留的状态文件(解决某个 rake 任务因残留文件报错的问题)

五、长期改进措施

已实施

1. 提高内核保留内存

vm.min_free_kbytes = 524288   # 66MB → 512MB

写入 /etc/sysctl.conf 持久化。保证内存紧张时内核关键路径(网络收包、NFS)不会先于 OOM Killer 崩溃,系统有机会正常回收内存而非直接网络瘫痪。

2. 限制 Passenger Worker 数量

基于一周监控数据(采样间隔 5 分钟),实际并发峰值仅 56 个 worker,请求队列始终为 0——说明 110 个配置严重过剩,其中约 88 个从未处理过任何请求。

配置项 调整前 调整后
PassengerMaxPoolSize 110 80
PassengerMinInstances 110 20

效果:峰值内存从约 41GB 降到约 25GB,低峰期只保留 20 个常驻进程(空闲时释放内存),有请求时自动扩容到最多 80。

3. 优化 Delayed Job Worker 配置

原配置 36 个 worker(72 进程,含监控进程),分池如下:

  • tracking: 1
  • log_import: 5
  • log_export: 16 ← 一周零任务,代码中无任何引用
  • 通配队列: 14

数据分析发现:log_import 是唯一的重负载队列(单日处理约 5000 次,凌晨 4-6 点为高峰),而 log_export 队列已废弃(日志归档改由其他机制处理)。调整为:

  • tracking: 1
  • log_import: 10(翻倍,缓解凌晨积压和慢导入)
  • 通配队列: 5
  • log_export: 删除

worker 从 36 降到 16(进程从 72 降到 32)。staging 实测验证:单进程内存一致,新配置省约 6.6GB(空闲态),运行态节省更多。

建议

  • Swap 增加到 8-16GB,提供 OOM 前的缓冲
  • NFS 挂载使用 soft,timeo=10 选项,防止存储无响应时拖死整个系统
  • 给应用中同步的 ping/getent 外部调用加超时和缓存,减少不必要的子进程

六、关键经验

  1. 内存监控要看并发峰值,不是配置值。 一周监控证明 110 配置里 80% 是浪费,实际峰值才 56。用数据说话,避免凭感觉配置。

  2. vm.min_free_kbytes 是容易被忽视的关键参数。 内存耗尽时,”内核先崩还是 OOM Killer 先动”决定了是”网络瘫痪整机卡死”还是”杀几个进程自愈”。保留内存太小会让故障从”可恢复”变成”不可恢复”。

  3. 进程统计要小心 grep 把自己算进去。 ps | grep xxx | wc -l 常把执行命令的 shell/grep 自身算入,统计进程数时应按进程名精确匹配或用 pgrep。

  4. 监控进程(monitor)的内存开销不能忽略。 每个 delayed_job worker 配一个 monitor,monitor 内存和 worker 几乎一样大。算内存账时漏掉 monitor 会严重低估。


七、通俗版复述

发生了什么

有一台服务器突然”死机”了——远程连不上,上面跑的服务也全停了,最后只能强制重启才救回来。

为什么会这样

打个比方,服务器的内存就像一个房间,里面要住很多”工人”(处理网页请求的进程)。问题出在几个方面凑一起:

  1. 工人雇太多了。 本来房间最多舒服地住 50 来个工人就够干活了,但当初设置成强制塞进 110 个,而且大部分工人整天闲着没活干,却一直占着床位(内存)。

  2. 有个坏掉的程序在不停重启。 一个负责打包日志的程序每 25 秒就崩溃重启一次,每次重启都要占一大块地方,持续给本就拥挤的房间添乱。

  3. 应急空间太小。 系统有个”备用仓库”(swap)但太小了,很快就塞满。

  4. 最致命的一点: 操作系统给自己留的”应急口粮”(内核保留内存)只有 66MB,太少了。当房间被工人挤爆时,操作系统连”收发网络消息”这种最基本的事都没内存干了。结果就是网络先瘫痪,连带存储(NFS)也卡死,整台机器彻底僵住——连”把多余工人赶出去”这个自救动作都做不了了。

怎么修好的

  1. 先强制重启,把机器救活。

  2. 把操作系统的应急口粮从 66MB 提到 512MB。 这样以后即使房间又挤,操作系统至少还能喘气、还能把多余的工人赶出去自救,不会直接僵死。

  3. 把工人数量从 110 砍到最多 80,平时只留 20 个。 用一周的实际数据证明了:就算最忙的时候也就用到 56 个,110 纯属浪费。平时留 20 个待命,忙起来再自动加人,闲下来自动遣散,省内存。

  4. 清理另一批后台工人(delayed job)。 发现有一组 16 个工人专门负责一个早就没人用的任务,白白占着内存;而真正忙的那个任务反而人手不够。于是把闲的裁掉、给忙的加人,总人数从 36 减到 16。

得到的教训

  • 配多少资源要看实际用量,别拍脑袋。 数据显示 110 个工人里 80 个从没干过活。
  • 操作系统的”应急口粮”这个小参数,关键时刻决定生死。 太小会让”本来能自救的故障”变成”彻底死机”。
  • 数人数的时候别把自己数进去。 用命令统计进程数时,经常会把”执行统计这条命令”本身也算进去,导致数字虚高。
上一篇:
测试 Worker 节点日志与结果清理问题分析报告
下一篇:
ResultWeb 邮件发件人与收件人配置拆分
本文目录
本文目录