Node.js大文件上传到MongoDB阻塞事件循环和工作池

f87krz0w  于 2023-04-20  发布在  Go
关注(0)|答案(4)|浏览(171)

所以我想通过Node.js服务器使用Express、Mongoose和Multer的GridFS存储引擎上传大的CSV文件到mongoDB云数据库,但是当文件上传开始时,我的数据库就无法处理任何其他API请求**。例如,如果在文件上传时,不同的客户端请求从数据库获取用户,服务器会收到请求,并尝试从MongoDB云获取用户,但请求会被卡住,因为大文件上传会消耗掉所有的计算资源。因此,客户端执行的get请求将不会返回用户**,直到正在进行的文件上传完成。
我理解如果一个线程花了很长时间来执行回调(事件循环)或任务(Worker),那么它被认为是“阻塞”的,Node.js在事件循环中运行JavaScript代码,同时提供一个Worker Pool来处理昂贵的任务,如文件I/O。在任何给定时间与每个客户端关联的工作必须是“小的”,我的目标应该是
最小化任务时间的变化**。这背后的原因是,如果一个Worker的当前任务比其他任务昂贵得多,它将无法处理其他挂起的任务,从而将工作者池的大小减1,直到任务完成。
换句话说,执行大文件上传的客户端正在执行一个昂贵的任务,这会降低Worker Pool的吞吐量,从而降低服务器的吞吐量。根据上述博客文章,当每个子任务完成时,它应该提交下一个子任务,当最后一个子任务完成时,它应该通知提交者。这样,在长任务(大文件上传)的每个子任务之间,Worker可以从一个较短的任务工作在一个子任务上,从而解决阻塞问题。

但我不知道如何在实际代码中实现这个方案,有没有具体的分区函数可以解决这个问题?我上传文件是否需要使用特定的上传架构或multi-gridfs-storage以外的节点包?请帮助

以下是我目前使用Multer的GridFS存储引擎的文件上传实现:

// Adjust how files get stored.
   const storage = new GridFsStorage({
       // The DB connection
       db: globalConnection, 
       // The file's storage configurations.
       file: (req, file) => {
           ...
           // Return the file's data to the file property.
           return fileData;
       }
   });

   // Configure a strategy for uploading files.
   const datasetUpload = multer({ 
       // Set the storage strategy.
       storage: storage,

       // Set the size limits for uploading a file to 300MB.
       limits: { fileSize: 1024 * 1024 * 300 },
    
       // Set the file filter.
       fileFilter: fileFilter,
   });

   // Upload a dataset file.
   router.post('/add/dataset', async (req, res)=>{
       // Begin the file upload.
       datasetUpload.single('file')(req, res, function (err) {
           // Get the parsed file from multer.
           const file = req.file;
           // Upload Success. 
           return res.status(200).send(file);
       });
   });
kyks70gy

kyks70gy1#

我认为这个问题是来源于buffer。因为buffer必须接收所有chunk,然后将整个buffer发送给consumer,所以缓冲需要很长时间可以解决这个问题,所以流允许我们在数据从源到达立即处理数据,并做通过缓冲数据不可能做的事情然后一次性处理**。我在multer GitHub页面找到了storage.fromStream()方法,并上传了一个122MB的文件进行测试,对我来说很有效,感谢Node.js流,每一个数据块在收到后都会被消耗并保存到云数据库中。上传的总时间不到1分钟,并且服务器可以在上载期间容易地响应其他请求。

const {GridFsStorage} = require('multer-gridfs-storage');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const express = require('express');
const fs = require('fs');
const connectDb = require('./connect');
const app = express();
 
const storage = new GridFsStorage({db:connectDb()});

app.post('/profile', upload.single('file'), function (req, res, next) {
  const {file} = req;
  const stream = fs.createReadStream(file.path); //creates stream
  storage.fromStream(stream, req, file)
    .then(() => res.send('File uploaded')) //saves data as binary to cloud db
    .catch(() => res.status(500).send('error'));
});
app.get('/profile',(req,res)=>{
    res.send("hello");
})

app.listen(5000);
h6my8fg2

h6my8fg22#

所以经过几天的研究,我发现问题的根源不是Node.JS或我的文件上传实现。问题是MongoDB Atlas无法在处理文件上传工作负载的同时处理其他操作,例如从我的数据库中获取用户。正如我在问题帖子中所说,Node.js正在接收来自其他客户端的API调用,这是应该的,但是他们没有返回任何结果。我现在意识到这是因为他们在DB级别卡住了。一旦我切换到MongoDB的本地部署,问题就解决了。
根据this blog post about MongoDB Best Practices,相对于CPU数量的活动线程总数(即并发操作)会影响性能,因此会影响Node.js服务器的吞吐量。然而,我尝试使用最多8个vCPU的专用MongoDB集群(M50集群包),MongoDB Atlas仍然无法在处理其他客户端请求时上传文件
如果有人使用云解决方案使其工作,我想知道更多。谢谢。

nkoocmlb

nkoocmlb3#

我也遇到了类似的问题,为了解决这个问题,我(以某种方式)为MongoDB实现了多个连接。
所以上传操作将由一个新的MongoDB连接处理,在上传过程中,您仍然可以使用另一个连接查询数据库。https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs

wwtsj6pe

wwtsj6pe4#

你能管理架构/基础设施吗?如果是这样,这个挑战将通过不同的方法得到最好的解决。这实际上是无服务器解决方案的完美候选者,即Lambda。
Lambda不会在一台机器上并行运行任何请求。Lambda将一个请求分配给一台机器,直到请求完成,这台机器将不会接收任何其他流量。因此,您永远不会达到您现在遇到的限制。

相关问题