4 核 ARM 云服务器跑无头 Chrome:内存、字体与并发的那几个坑
把 headless Chrome 放到云服务器上跑,第一次能起来很容易,能连续跑一个月不炸不容易。我这边是一台 4 核 ARM 实例,24 小时按计划跑截图和表单自动化,中间踩过的坑基本都不是"性能不够",而是环境缺东西。下面四条是真正解决问题的那几条。
一、先解决字体,不然截图全是方块
最小化镜像里没有任何中文字体,截图出来的中文全部变成豆腐块。这个坑最隐蔽的地方在于:浏览器不报错、页面渲染成功、就是字没了。
不要装整套字体包,占空间又慢。只补常用字重和一个等宽字体就够:
apt-get install -y --no-install-recommends fonts-noto-cjk fonts-noto-cjk-extra fontconfig
fc-cache -fv
fc-list :lang=zh | head -3 # 能列出字体才算装好
装完必须重启一次浏览器进程,字体缓存是在进程启动时读的,热更新无效。
二、/dev/shm 默认 64MB,这是最常见的崩溃原因
容器里跑 Chrome 报 Target closed 或者标签页莫名崩溃,八成是共享内存不够。Chrome 渲染进程默认把位图放在 /dev/shm,Docker 默认只给 64MB。
两种改法,任选一种:
- 启动参数加
--disable-dev-shm-usage,让 Chrome 改用 /tmp,代价是稍微慢一点; - 容器启动时加
--shm-size=512m或更大,性能更好。
我选的是后者,因为截图是主要业务,不想在 IO 上让一步。
三、并发不要按 CPU 核数给,按内存给
一个空闲的 Chrome 实例大约吃 120MB 常驻,开一个标签页跑真实页面会涨到 300~500MB,复杂页面过 1GB 也正常。4 核 8G 的机器,按核数开 4 个实例很快就 OOM。
我的经验值:并发数 = 可用内存(GB) × 0.8 ÷ 0.6,也就是每实例按 600MB 预留。8G 机器给到 8 个上限,再配合队列串行化,稳定性比"开满再说"高一档。
# 每个 worker 一个独立 profile 目录,避免锁冲突
opts = [
"--headless=new",
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu",
"--font-render-hinting=none",
f"--user-data-dir=/tmp/chrome-{worker_id}",
]
四、一定要做僵尸进程回收
Chrome 是主进程 + 一堆子进程的模型,脚本异常退出时子进程会留下来。跑一周之后 ps aux | grep -c chrome 能到上百,内存被慢慢吃光,表现是"服务没挂但越来越慢"。
兜底做法是加一个定时清理:按 profile 目录反查进程,超过空闲阈值的整组杀掉。
# 找出超过 2 小时没被访问的 profile 并清理整组进程
for d in /tmp/chrome-*; do
[ $(( $(date +%s) - $(stat -c %Y "$d") )) -gt 7200 ] || continue
pkill -f "user-data-dir=$d" && rm -rf "$d"
done
清理脚本本身也会误杀正在跑的实例,所以目录的 mtime 要在每次任务前 touch 一次,用它当"心跳"。
最后一句
这套东西没有一行复杂代码,全是环境配置。真正省时间的思路只有一个:把每次"莫名其妙崩了"都当成环境问题先查一遍,查完记进部署脚本里。跑自动化任务这件事,稳定性是靠无聊的默认值堆出来的。
- 点赞
- 收藏
- 关注作者
评论(0)