Nodejs 读写大文件导致内存溢出(FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory)

Nodejs 读写大文件导致内存溢出(FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory)

菜鸟请教各位,下面的代码有问题吗?有没有另外一种可能,服务器网速慢,读写速度不一致导致内存溢出呢?

self._init(filePath, function() {
                var config = self.config;
                resp = self.resp;
                fReadStream = fs.createReadStream(filePath, {
                    encoding : 'binary',
                    bufferSize : 1024 * 1024,
                    start : config.startPos,
                    end : config.fileSize
                });
                fReadStream.on('data', function(chunk) {
                    resp.write(chunk, 'binary');
                });
                fReadStream.on('end', function() {
                    resp.end();
                });
            });


4 回复

Node.js 读写大文件导致内存溢出(FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory)

问题描述

在处理大文件时,使用Node.js读取并直接写入响应流可能会导致内存溢出。这个问题通常是因为数据在内存中累积过多,超过了Node.js进程的最大内存限制。

示例代码分析

你提供的代码片段试图从一个文件流中读取数据,并将这些数据直接写入HTTP响应流。尽管这种方式在小文件的情况下可能有效,但在处理大文件时可能会导致内存溢出。

self._init(filePath, function() {
    var config = self.config;
    resp = self.resp;
    fReadStream = fs.createReadStream(filePath, {
        encoding : 'binary',
        bufferSize : 1024 * 1024,
        start : config.startPos,
        end : config.fileSize
    });
    fReadStream.on('data', function(chunk) {
        resp.write(chunk, 'binary');
    });
    fReadStream.on('end', function() {
        resp.end();
    });
});

问题原因

  1. 数据累积resp.write(chunk) 在每次接收到数据块时都会将数据写入内存缓冲区。如果数据量过大,可能会导致内存不足。
  2. 流式处理:虽然fs.createReadStream是流式读取,但如果没有正确处理流式写入,数据仍然可能在内存中累积。

解决方案

为了防止内存溢出,可以采用流式处理的方式来处理大文件。以下是一个改进后的示例代码:

const fs = require('fs');
const http = require('http');

function serveFile(req, res, filePath) {
    const readStream = fs.createReadStream(filePath);
    readStream.pipe(res); // 使用管道将读取流直接连接到响应流

    readStream.on('error', (err) => {
        console.error(`Error reading file: ${err}`);
        res.statusCode = 500;
        res.end('Internal Server Error');
    });

    res.on('error', (err) => {
        console.error(`Error writing response: ${err}`);
        readStream.destroy();
    });
}

// 示例用法
http.createServer((req, res) => {
    serveFile(req, res, '/path/to/large/file');
}).listen(3000, () => {
    console.log('Server listening on port 3000');
});

解释

  1. 使用管道(pipe):通过readStream.pipe(res),可以直接将读取流的数据直接传递给响应流,避免在内存中累积大量数据。
  2. 错误处理:增加了对读取和写入过程中的错误处理,确保程序的健壮性。

通过这种方式,可以有效地解决因内存溢出而导致的问题。


流式处理一般不会导致内存溢出,就怕异步调用导致的循环嵌套太深会导致内存溢出,

请检查闭包。

你的问题主要是因为 Node.js 在处理大文件时,由于文件读取和响应写入的速度不匹配,可能会导致内存溢出。这是因为 resp.write(chunk) 并不会立即发送数据,而是会先将数据存储在内部缓冲区中,直到缓冲区满或者调用 resp.end() 才会真正发送数据。如果响应发送的速度跟不上文件读取的速度,就会导致大量的数据堆积在内存中,最终导致内存溢出。

为了解决这个问题,可以使用流式处理的方式来优化文件的读取和响应的发送。下面是一个改进后的代码示例:

const fs = require('fs');
const http = require('http');

function handleRequest(req, res) {
    const filePath = 'path/to/large/file'; // 替换为实际文件路径

    const fileStream = fs.createReadStream(filePath);
    fileStream.on('open', () => {
        res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
        fileStream.pipe(res);
    });

    fileStream.on('error', (err) => {
        res.writeHead(500);
        res.end(`Server Error: ${err.message}`);
    });
}

const server = http.createServer(handleRequest);
server.listen(3000, () => {
    console.log('Server is listening on port 3000');
});

在这个示例中,我们使用了管道(pipe)来连接读取流和响应流。这样做的好处是它自动处理了读取和响应之间的速率匹配问题,并且不会将所有数据都加载到内存中,从而避免了内存溢出的风险。

总结:

  • 使用管道(pipe)来连接读取流和响应流。
  • 管道会自动处理速率匹配问题,避免内存溢出。
回到顶部