头图
Before doing a Node.js to achieve the function of fragment upload . At that time, the front-end used file slicing and then uploaded concurrently, which greatly improved the upload speed. Promise.race() was used to manage the concurrent pool, which effectively avoided browser memory exhaustion.

The current problem: Node.js server merges large files and shards run out of memory, causing the service to restart

server code

 const ws = fs.createWriteStream(`${target}/${filename}`); // 创建将要写入文件分片的流
const bufferList = fs.readdirSync(`${STATIC_TEMPORARY}/${filename}`); // 读取到分片文件名的列表[1,2,3...] 每个分片1M大小

// 2. 不会阻塞EventLoop
bufferList.forEach((hash, index) => {
  fs.readFile(`${STATIC_TEMPORARY}/${filename}/${index}`, (err, data) => {
       ws.write(data);
  });
});

Server configuration: RAM: 1024MB 1vCPU (running some other services)

The test found that as long as the uploaded file exceeds 300M, the memory exhaustion service will be restarted when the shards are merged, and the shard merge cannot be completed at all.

Solution: Can you control the number of concurrent files read in the loop? This way, there will be no large number of files read into memory at the same time, causing the service to crash.

Try using Promise.race controls like the front end:

 const ws = fs.createWriteStream(`${target}/${filename}`); // 创建将要写入文件分片的流
const bufferList = fs.readdirSync(`${STATIC_TEMPORARY}/${filename}`); // 读取到分片文件名的列表[1,2,3...] 每个分片1M大小

// Promise.race 并发控制
const pool = [];
let finish = 0; // 已经写入完成的分片数量

// 使用Promise包裹读取文件的异步操作
const PR = (index) => {
  return new Promise((resolve) => {
    fs.readFile(`${STATIC_TEMPORARY}/${filename}/${index}`, (err, data) => {
      ws.write(data)
      resolve({});
    });
  });
};

(async function easyRead() {
  for (let i = 0; i < bufferList.length; i ++) {
    const task = PR(i).then(val => {
      finish+=1
      const index = pool.findIndex(t => t === task);
      pool.splice(index);
      if (finish === bufferList.length) {
        ws.close();
      }
    });
    pool.push(task);
    if (pool.length === 1) { // 这里并发数量只能是1 否则分片写入是乱序的 格式会被损坏
      await Promise.race(pool);
    }
  }
})()

Then the magic happens, we use Promise.race() in the for loop to control the number of files read into memory at the same time.

Test again, the service will not crash when merging shards, even 1 G file shards can be merged in about 3 seconds.

async, await

I believe this is the more commonly used method for dealing with asynchrony, because it is more elegant to write. Our Promise.race above can also be replaced with it to make the code look more concise and clear:

 const ws = fs.createWriteStream(`${target}/${filename}`); // 创建将要写入文件分片的流
const bufferList = fs.readdirSync(`${STATIC_TEMPORARY}/${filename}`); // 读取到分片文件名的列表[1,2,3...] 每个分片1M大小

let finish = 0; // 已经写入完成的分片数量

// 使用Promise包裹读取文件的异步操作
const PR = (index) => {
  return new Promise((resolve) => {
    fs.readFile(`${STATIC_TEMPORARY}/${filename}/${index}`, (err, data) => {
      ws.write(data)
      resolve({});
    });
  });
};

(async function easyRead() {
  for (let i = 0; i < bufferList.length; i ++) {
    await PR(i)
    finish +=1
    if (finish === bufferList.length) {
        ws.close();
    }
  }
})()

The same effect can be achieved, does it look much clearer?

Summarize

There are many people who know Promise.race(), and there are many such interview questions, but not many can be used to solve practical problems in practice.

Hope this article can help you.

The article was first published on IICCOM-personal blog | technical blog "Using Promise.race() to control concurrency"


来了老弟
508 声望31 粉丝

纸上得来终觉浅,绝知此事要躬行


引用和评论

0 条评论