NodeJS Sharp 节点包内存消耗问题

pla*_*ter 7 memory-leaks image-processing node.js sharp

我正在开发一个用于sharp node package调整 JPEG/JPG 图像大小的 NodeJS 项目。

问题是Node进程不断地将处理后的文件添加到内存中并且从不释放它。

因此,消耗的内存量随着每个请求而增加,并且永远不会被释放。

调试应用程序后,我意识到Sharp toBuffer API导致了该问题。

我尝试通过创建 acustom writable stream并用管道来使用替代解决方案sharp Duplex stream,但最终遇到了同样的问题。

我不明白我是否在这里遗漏了任何东西或者这是一个错误。

分享下面的代码(我删除了不需要的代码以使其紧凑)-

const { Writable } = require("stream");
const { createServer } = require("http");
const { readFileSync } = require("fs");
const sharp = require("sharp");

async function resizeJpeg(input_buffer) {
    // initialise file, the response object
    const file = { normalisedImage: null, originalImage: null };
    // initialise sharp instance using original image buffer
    let image = sharp(input_buffer);
    // set the original image metadata
    file.originalImage = await image.metadata();
    file.originalImage.quality = 85;

    // generate buffer using sharp resize API with default quality & dimensions.
    // ############### THIS IS WHERE MEMORY CONSUMPTION OCCURS ###############

    // APPROACH 1 (SHARP toBuffer API)
    const buffer = await image.resize(2000, 798).jpeg({ quality: 85 }).toBuffer();
    // APPROACH 1 ENDS 

    // APPROACH 2 (CUSTOM WRITABLE STREAM)
    // const buffer = await sharpToBuffer(image.resize(2000, 798).jpeg({ quality: 85 }));
    // APPROACH 2 ENDS

    // set resized image metadata
    file.normalisedImage = await sharp(buffer).metadata();
    file.normalisedImage.quality = 85;
    return file;
}

// converts sharp readable stream to buffer using custom writable buffer
async function sharpToBuffer(readable) {
    return new Promise((resolve, reject) => {
        const writable = new WriteStream().on("finish", () => resolve(Buffer.concat(writable.bufferChunks)));
        readable.pipe(writable);
    });
}

// simple writable stream
class WriteStream extends Writable {
    constructor() { super(); this.bufferChunks = [] }
    _write(chunk, encoding, next) { this.bufferChunks.push(chunk); next(); }
}

createServer(async (request, response) => {
    // ignore favicon calls
    if (request.url.indexOf("favicon.ico") > -1) { return response.end(); }
    // trigger resize call and pass buffer of original image file
    const { normalisedImage, originalImage } = await resizeJpeg(readFileSync(`${__dirname}/30mb.jpg`));
    // respond stringified json
    response.end(
        JSON.stringify({
            normalisedImage: { size: normalisedImage.size, width: normalisedImage.width, height: normalisedImage.height, quality: normalisedImage.quality },
            originalImage: { size: originalImage.size, width: originalImage.width, height: originalImage.height, quality: originalImage.quality }
        }, null, 4));
}).listen(3000, () => console.log("server started"));
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,resizeJpeg function这两种方法都已实施。

要实现此功能,您只需确保30mb.jpg文件存在于同一目录中。

我使用的图像可以在这里找到

如果您使用的是 Linux,则top command可以使用以下命令来监视内存使用情况,假设文件名为so.js-

顶部ps -ef | grep 'so.js' | awk '{print $2}' | sed 's/.*/-p &/' | xargs echo -c

Ped*_*eno 3

这似乎是与他们的内存分配器有关的问题。

在他们的官方文档https://sharp.pixelplumbing.com/install#linux-memory-allocator中,他们建议改用jemalloc来解决这个问题。

简而言之,要解决这个问题,您必须:

apt-get install libjemalloc2

export ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
Run Code Online (Sandbox Code Playgroud)

您可能还需要这个(我不确定):

rm -rf /var/lib/apt/lists/*
Run Code Online (Sandbox Code Playgroud)

资料来源:

https://github.com/lovell/sharp/issues/1041

https://github.com/lovell/sharp/issues/955#issuecomment-475532037