
?️ 服务器健康看门狗:自动清理Chrome进程残留
? 2025年5月
?️
运维实战
Chrome
MySQL
监控
⏱️ 阅读约 6 分钟
在自动化运维和Web爬虫场景中,Playwright 驱动的无头浏览器是得力工具。但一次异常退出留下的 65个Chrome僵尸进程,足以让服务器负载飙到 66,内存吃掉 2GB。本文分享一套实战验证的 服务器健康看门狗 方案:自动检测负载与内存水位,清理残留Chrome进程,诊断MySQL健康状况。
一、触发机制:自动健康检查
看门狗脚本周期性运行(推荐 cron 每分钟或每两分钟),一旦检测到以下任意条件即触发全套健康检查流程:
? 触发条件
? 服务器负载(/proc/loadavg 1分钟平均值)> 30
? 内存使用率(已用/总量)> 80%
条件触发的核心检测代码如下:
bash
LOAD=$(awk '{print $1}' /proc/loadavg | cut -d'.' -f1)
MEM=$(free | awk '/Mem:/ {printf "%.0f", ($3/$2)*100}')
if [ "$LOAD" -gt 30 ] || [ "$MEM" -gt 80 ]; then
echo "[ALERT] 负载=$LOAD 内存=$MEM% => 触发健康检查"
bash /opt/health-watchdog/run.sh
fi
? 设计考量: 负载阈值设为 30(按4核物理机约单核7.5)确保在明显异常时才触发,避免频繁报警。内存阈值80%留出swap/突发缓冲。
二、Chrome 进程残留:一次真实故障复盘
2.1 故障现场
某次 Playwright 自动化任务因网络超时异常退出,browser.close() 未能执行。监控面板显示:
? 故障指标
ps aux | grep chrome | wc -l → 65 个进程
系统负载(1min)→ 66.3
内存占用 → 2.1 GB(总量4GB,已超50%被Chrome吞噬)
CPU 使用率 → 持续 95%+ iowait 升高
观察进程树可以发现 Playwright 启动了 一个主浏览器进程 及大量 --type=renderer / --type=utility / --type=gpu-process 的子进程。正常退出时所有进程链式关闭;异常退出则只剩进程残骸。
┌─ Chrome 进程树(正常) ┌─ 残留状态
│ │
PID 1234 chrome PID 1234 chrome (defunct?)
├── PID 1235 chrome --type=renderer ├── PID 1235 chrome --type=renderer
├── PID 1236 chrome --type=gpu-process ├── PID 1236 chrome --type=gpu-process
├── PID 1237 chrome --type=utility ├── PID 1237 chrome --type=utility
├── ... 更多子进程 ... ├── ... 全部存活,无父进程回收 ...
│ │
└── 所有进程随主进程退出而终止 └── 持续消耗CPU和内存
2.2 清理策略:杀主进程 → 子进程自动回收
Chrome 进程间有父子关系。杀掉所有带 --type= 参数的子进程是低效的——更优策略是 只杀主进程(无 --type= 参数的 chrome 进程),操作系统会自动回收其子进程。
✅ 核心清理命令
查找所有 chrome 进程中 不包含 --type= 的,即主浏览器进程 → 逐个 kill
bash
func() {
local count=0
for pid in $(ps aux | grep '[c]hrome' | grep -v '--type=' | awk '{print $2}'); do
echo "[KILL] 主进程 PID=$pid"
kill -15 $pid 2>/dev/null
count=$((count + 1))
done
sleep 2
for pid in $(ps aux | grep '[c]hrome' | awk '{print $2}'); do
kill -9 $pid 2>/dev/null
done
echo "[OK] 已清理 $count 个主进程及所有子进程"
}
为什么不需要逐个杀子进程?因为 Linux 内核的 进程组(process group)机制:发送 SIGTERM 到主进程后,init/systemd 会接管孤儿进程并发送 SIGHUP。绝大多数子进程在 1-2 秒内自行退出。补杀阶段的 kill -9 是兜底保障。
三、MySQL 诊断:错误日志 + 数据目录
看门狗的第二个职责是诊断MySQL健康。重点关注两类指标:
3.1 分析 Aborted connection
MySQL 错误日志中 Aborted connection 记录了大量连接失败信息,常见原因:密码错误、用户无权限、连接超时、max_connections 耗尽。看门狗解析最近 N 条错误日志,统计 Aborted connection 出现频率。
bash
if [ -f /var/log/mysql/error.log ]; then
LOG=/var/log/mysql/error.log
elif [ -f /var/log/mysqld.log ]; then
LOG=/var/log/mysqld.log
else
LOG=$(mysql -e "SHOW VARIABLES LIKE 'log_error'" 2>/dev/null | awk '{print $2}' | tail -1)
fi
ABORTED=$(tail -100 $LOG | grep -c 'Aborted connection')
echo "[MySQL] 近100行错误日志中 Aborted connection: $ABORTED 次"
if [ $ABORTED -gt 10 ]; then
echo "[WARN] Aborted connection 异常增多,请检查连接配置"
tail -100 $LOG | grep 'Aborted connection' | tail -5
fi
3.2 无密码读取数据目录大小
在不依赖 MySQL 登录凭据的情况下,直接通过 du 读取 MySQL 数据目录获取磁盘占用,这是最轻量、最可靠的方式:
bash
DATADIR=$(mysql -e "SHOW VARIABLES LIKE 'datadir'" 2>/dev/null \
| awk '{print $2}' | tail -1)
if [ -z "$DATADIR" ]; then
DATADIR=/var/lib/mysql
fi
SIZE=$(du -sh $DATADIR 2>/dev/null | awk '{print $1}')
echo "[MySQL] 数据目录: $DATADIR, 大小: $SIZE"
echo "各数据库占用TOP5:"
du -sh $DATADIR/*/ 2>/dev/null | sort -rh | head -5
四、完整看门狗脚本
将上述模块整合为一个可执行的健康检查脚本:
bash
THRESHOLD_LOAD=30
THRESHOLD_MEM=80
LOG_FILE=/var/log/health-watchdog.log
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE; }
LOAD=$(awk '{print $1}' /proc/loadavg | cut -d'.' -f1)
MEM=$(free | awk '/Mem:/ {printf "%.0f", ($3/$2)*100}')
if [ "$LOAD" -le 30 ] && [ "$MEM" -le 80 ]; then
exit 0
fi
log "=== 触发健康检查 (负载=$LOAD, 内存=$MEM%) ==="
log "[Chrome] 开始清理..."
for pid in $(ps aux | grep '[c]hrome' | grep -v '--type=' | awk '{print $2}'); do
log " Kill 主进程 PID=$pid"
kill -15 $pid 2>/dev/null || true
done
sleep 2
LEFT=$(ps aux | grep '[c]hrome' | wc -l)
if [ "$LEFT" -gt 0 ]; then
kill -9 $(ps aux | grep '[c]hrome' | awk '{print $2}') 2>/dev/null || true
log " 补杀剩余 $LEFT 个进程"
fi
log "[Chrome] 清理完成"
LOG_PATH=$(mysql -NBe "SHOW VARIABLES LIKE 'log_error'" 2>/dev/null | awk '{print $2}')
LOG_PATH=${LOG_PATH:-/var/log/mysql/error.log}
ABORTED=$(tail -100 $LOG_PATH 2>/dev/null | grep -c 'Aborted connection')
log "[MySQL] Aborted connection: $ABORTED (近100行)"
DATADIR=$(mysql -NBe "SHOW VARIABLES LIKE 'datadir'" 2>/dev/null | awk '{print $2}')
DATADIR=${DATADIR:-/var/lib/mysql}
SIZE=$(du -sh $DATADIR 2>/dev/null | awk '{print $1}')
log "[MySQL] 数据目录: $DATADIR, 大小: $SIZE"
log "=== 健康检查完成 ==="
五、阈值配置与生产调优
| 参数 |
建议值 |
说明 |
| 负载阈值 |
CPU核心数 × 7 ~ × 10 |
4核服务器设为 30~40;8核设为 60~80 |
| 内存阈值 |
75% ~ 85% |
过低易误报,过高有OOM风险 |
| 检查间隔 |
1 ~ 2 分钟 |
cron 定时,无需更短 |
| Aborted 告警 |
> 10次/百行 |
配合日志轮转调整窗口 |
| 日志保留 |
7 天轮转 |
使用 logrotate 避免磁盘满 |
⚙️ cron 配置示例
* * * * * root /opt/health-watchdog/run.sh(每分钟执行)
或使用 systemd timer 替代 cron。
六、预防建议:源头避免 Chrome 残留
清理是治标,预防才是治本。以下两条最佳实践可以从源头避免 Chrome 残留问题:
✅ 最佳实践 1:browser.quit() 务必 try/finally
无论业务逻辑是否异常,finally 块保证浏览器实例一定被关闭:
python
browser = playwright.chromium.launch()
try:
page = browser.new_page()
page.goto("https://example.com")
finally:
browser.close()
✅ 最佳实践 2:脚本开头 pkill 旧进程
在 Playwright 脚本的最开始(launch 之前),强制清理上一轮可能残留的 Chrome 进程:
python
import subprocess
subprocess.run(
["pkill", "-f", "chrome"],
capture_output=True
)
browser = playwright.chromium.launch()
⚠️ 注意:pkill -f chrome 会匹配所有含 "chrome" 的进程名,适合在脚本开头使用;日常运维中请使用前面提到的精准杀主进程策略。
七、总结
本文从一次真实故障出发,完整介绍了 服务器健康看门狗 的构建思路:
- 自动触发:负载 > 30 或内存 > 80%,基于 /proc/loadavg 和 free 命令
- Chrome 清理:杀主进程(无
--type= 参数)→ 子进程自动被 OS 回收
- MySQL 诊断:分析错误日志中 Aborted connection、无密码读取数据目录大小
- 预防体系:
browser.quit() 用 try/finally 包裹、脚本开头 pkill 旧进程
这套方案已在生产环境稳定运行数月,累计自动清理 Chrome 残留 200+ 次,MySQL 异常连接告警 30+ 次,将系统因 Playwright 异常导致的故障时间从 小时级 降至 分钟级。
? 扩展阅读
— 《Playwright 官方文档:Browser Context 生命周期管理》
— 《Linux 进程组与孤儿进程回收机制》
— 《MySQL Error Log 详解:Aborted connection 的七种成因》
服务器健康看门狗 · 实战技术博客 · 持续运维不宕机 ?