用worker_threads处理图片缩略图,我踩到的内存坑
上周在 Node 18.16 上跑一个批量生成缩略图的服务,输入是 2000 张 5MB 左右的 JPEG,输出 200x200 的 webp。用 sharp 在主线程一张张处理,内存峰值直接飙到 1.2GB,处理速度还慢。后来换成 worker_threads 池,峰值降到 400MB 左右,吞吐量大概翻了一倍。
做法不复杂:先建一个固定大小的 worker 池,大小取 os.cpus().length - 1,避免把主线程饿死。主线程只负责读文件和分发路径,worker 里用 sharp 处理。
// main.js
import { Worker } from 'node:worker_threads';
import os from 'node:os';
const poolSize = Math.max(1, os.cpus().length - 1);
const workers = [];
for (let i = 0; i < poolSize; i++) {
workers.push(new Worker('./worker.js'));
}
let idx = 0;
function next() {
if (idx >= files.length) return Promise.resolve();
const file = files[idx++];
const worker = workers[idx % poolSize];
return new Promise((resolve, reject) => {
worker.once('message', resolve);
worker.once('error', reject);
worker.postMessage(file);
}).then(next);
}
worker 里这样写:
// worker.js
import { parentPort } from 'node:worker_threads';
import sharp from 'sharp';
parentPort.on('message', async (file) => {
try {
await sharp(file)
.resize(200, 200, { fit: 'inside' })
.webp({ quality: 80 })
.toFile(file.replace(/\.jpg$/, '.webp'));
parentPort.postMessage({ ok: true });
} catch (e) {
parentPort.postMessage({ ok: false, error: e.message });
}
});
为什么能省内存:sharp 底层是 libvips,它有自己的线程池和缓存,默认缓存上限是 50MB,但每个 worker 实例都会独立占一份缓存和 V8 堆。主线程串行处理时,这些缓存和堆反复增长,GC 跟不上;拆成多个 worker 后,每个 worker 的堆更小,缓存也分散了,整体峰值反而下降。代价是进程启动时多花几十毫秒创建 worker,且 sharp 的缓存参数需要调,我设了 sharp.cache(false) 和 sharp.concurrency(1),否则 worker 内部还会再开线程,跟池抢 CPU。
哪里会失效:如果单张图特别大(比如 50MB 的 TIFF),worker 池反而会让内存峰值叠加,因为每个 worker 同时持有一张大图。这时候应该限制并发数,或者用 sharp 的流式接口逐块处理。另外,worker 里抛出的异常如果不 catch,整个 worker 会退出,池子就少了一个,我一开始没处理,跑一半发现吞吐掉得厉害,查了半天。
你们在处理大量图片时,是倾向于用 worker 池还是直接上队列加多进程?有没有遇到 sharp 缓存跟 worker 抢资源的情况?
0 条评论
还没有评论,写下第一条。