CentOS 6.7下ImageMagick convert内存耗尽问题求解
针对你在CentOS 6.7上运行自定义爬虫,大量调用bash脚本处理TIFF文件导致convert/gs进程耗尽内存的问题,结合我处理类似大规模并发场景的经验,给你几个分层解决的方案:
一、限制所有convert/gs实例的总内存使用(兜底方案)
你提到单个进程的limit选项不够用,CentOS 6支持的**cgroups(控制组)**正好可以解决这个问题——它能限制一组进程的总内存(包括swap)使用量。具体步骤如下:
创建内存控制组
# 创建专门给ImageMagick用的内存控制组目录 mkdir -p /sys/fs/cgroup/memory/imagemagick-group设置总内存和swap上限
假设你想给所有convert/gs实例分配最多16GB内存+4GB swap(根据你的64GB总内存调整数值):# 限制总内存为16GB(16*1024*1024*1024 = 16106127360字节) echo 16106127360 > /sys/fs/cgroup/memory/imagemagick-group/memory.limit_in_bytes # 限制内存+swap总容量为20GB echo 21474836480 > /sys/fs/cgroup/memory/imagemagick-group/memory.memsw.limit_in_bytes修改脚本,让convert和tesseract在控制组内运行
用cgexec命令把convert和tesseract的调用包裹起来,这样它们的子进程(比如gs)也会自动继承控制组的内存限制:# 替换原来的convert调用 cgexec -g memory:imagemagick-group /usr/bin/convert -density 288 "$IMAGE_INPUT" -resize 75% -quality 100 -append jpeg:$TMP_FOLDER/$TFN; # 替换原来的tesseract调用 cgexec -g memory:imagemagick-group /usr/local/bin/tesseract $TMP_FOLDER/$TFN $TMP_FOLDER/$TFN.out;如果你想让整个脚本都在控制组内运行,也可以在爬虫调用脚本时直接用
cgexec,比如:cgexec -g memory:imagemagick-group ./your-script.sh input.tiff output.txt
二、控制脚本并发数(从根源减少内存占用)
大量并发调用是内存耗尽的核心原因,限制同时运行的脚本实例数能直接降低内存压力。这里给你两个简单的实现方式:
方式1:用GNU Parallel调度并发
如果你的爬虫可以批量生成待处理的文件列表,用GNU Parallel来控制同时运行的脚本数量非常方便:
# 假设你有一个input.txt文件,每行是输入文件路径,output.txt对应输出路径 parallel -j4 ./your-script.sh {} {.}.txt :::: input.txt
其中-j4表示最多同时运行4个脚本实例,你可以根据内存占用情况调整这个数字(比如从4开始测试,逐步找到最优值)。
方式2:用文件锁实现并发控制
如果爬虫是动态调用脚本(无法批量调度),可以在脚本开头加一段文件锁逻辑,限制同时运行的实例数:
# 脚本开头添加以下代码,最多允许4个实例同时运行 LOCK_BASE="/tmp/script-concurrent-locks" mkdir -p "$LOCK_BASE" # 生成4个锁文件,每个进程随机抢占一个锁 LOCK_FILE="$LOCK_BASE/lock-$(( $$ % 4 ))" # 尝试获取锁,拿不到就直接退出(避免排队等待,也可以改成阻塞等待) exec 9<>"$LOCK_FILE" if ! flock -n 9; then echo "Too many concurrent instances, exiting..." >&2 exit 1 fi
三、优化脚本本身,减少单进程内存占用
除了外部限制,优化脚本里的convert参数也能降低单个进程的内存消耗:
调整图片处理参数,减少内存负载
你当前用-density 288然后-resize 75%,其实可以直接把density设为216(288*0.75),这样生成的图片尺寸完全一致,但处理时的像素数更少,内存占用降低:/usr/bin/convert -density 216 "$IMAGE_INPUT" -quality 100 -append jpeg:$TMP_FOLDER/$TFN;及时清理临时文件,避免残留
现在你的临时文件靠cron清理,建议在脚本结束时主动删除临时文件,既节省磁盘空间,也避免意外残留:# 在脚本开头添加trap,确保即使脚本出错也删除临时文件 TFN=`cat /dev/urandom | tr -cd 'a-f0-9' | head -c 32`; trap 'rm -f "$TMP_FOLDER/$TFN" "$TMP_FOLDER/$TFN.out.txt"' EXIT
方案优先级建议
- 先优化脚本参数(步骤三),这是成本最低且立竿见影的优化;
- 然后控制脚本并发数(步骤二),从根源上减少内存的总占用;
- 最后用cgroups做兜底(步骤一),确保即使并发控制失效,也不会耗尽系统内存。
内容的提问来源于stack exchange,提问作者skatz

