使用 Azure NodeJS Functions 下载 100,000 个平均 50kB 文件的最快方法是什么?

What's the fastest way to download 100,000 files of 50kB average using Azure NodeJS Functions?

我正在编写一个备份程序,它应该复制所有客户在白天上传的所有文件并将它们存储在 Azure Blob 存储中。
我正在使用函数链接模式来支持一个又一个客户。我这样做是为了将每个客户的日志分组,但我想我也可以使用扇出扇入模式。

这是我的代码结构:

这是我的 DownloadFiles 函数:

const request = require('request');
const { getContainerURL, uploadStreamToBlob } = require('../blob-storage');
const log = require('../log');

function downloadFile(options, containerURL, id) {
  return new Promise((resolve, reject) => {
    request
      .get(options)
      .on('error', err => reject(err))
      .on('response', async (res) => {
        if (res.statusCode === 404) reject('HTTP 404');
        else if (res.statusCode !== 200) {
          reject(new Error(`got response code ${res.statusCode} for file ID ${id}\n${res.statusMessage}`));
        } else {
          try {
            await uploadStreamToBlob(containerURL, `photos/${id}`, res);
          } catch (err) {
            reject(new Error(`Cannot upload fileID to blob: ${id}`));
          }
          resolve();
        }
      });
  });
}

async function downloadFileWithRetry(options, containerURL, id, retry = false) {
  try {
    await downloadFile(options, containerURL, id);
    return null;
  } catch (error) {
    if (retry) return downloadFileWithRetry(options, containerURL, id, false);
    return error;
  }
}

module.exports = async function (context) {
  const { containerName, token, slice } = context.bindings.name;
  const containerURL = getContainerURL(containerName);
  const finalResult = {
    nbErrors: 0,
    nbFiles: 0,
  };

  const sliceSize = 8;
  for (let i = 0; i < slice.length; i += sliceSize) {
    const promises = slice.slice(i, i + sliceSize).map(async (file) => {
      const options = {
        url: `https://${process.env.HOSTNAME}/download/`,
        forever: true, // use the forever agent
        qs: { id: file.id, auth: token },
      };
      return {
        file,
        err: await downloadFileWithRetry(options, containerURL, file.id, true),
      };
    });
    const results = await Promise.all(promises);
    for (let j = 0; j < results.length; j++) {
      const res = results[j];
      if (!res.err) {
        finalResult.nbFiles++;
      } else {
        log(`Could not download file ID ${res.file.id}`);
        log(res.err);
        finalResult.nbErrors++;
      }
    }
  }

  return finalResult;
};

DownloadFiles 函数下载 250 个文件平均需要 15.7 秒。我可能可以通过将文件数量增加到 500 或 1,000 来提高吞吐量,但由于某些实例需要 250 秒,我恐怕会达到函数的最长持续时间 5 分钟。
有没有更好的方法来解决这个问题?

编辑 我还使用了 https.globalAgent 并将 keepAlive 设置为 true,它比使用永远的请求代理要快得多。

Azure 函数确实有 5 分钟的超时限制,首先它是消费计划的默认值,对于 App Service 计划 v2 默认值为 30 分钟。

根据您的情况,直接方法设置为 functionTimeout 属性 in host.json

这里是 timeout duration:

下面是host.json样本:

{
"functionTimeout": "00:05:00"
}

有关 functionTimeout 的更多信息,您可以参考此文档:functionTimeout

我建议您在函数应用程序中创建两个函数。一种将客户姓名放入队列的功能。另一个由该队列触发的函数。然后每个负载不受5分钟的限制,可以并行化。如果您喜欢 ping 我的方法,我会为您创建一个演示。

我建议您尝试一下,将您的 http 客户端放在函数范围之外,以便在一个函数主机内共享客户端。您可以看到示例 here. Maybe 答案将激励您进一步改进。

但是如果我在队列中插入 1,000 个文件的速度比下载文件的速度还快怎么办?
你的问题需要注意两点。 1) 您 can control 队列的并发执行数。 2) 每封邮件可以有多个文件名。

我将在 // 中有 600 个 DownloadFile 函数实例 运行 并且第 601 个实例将等到其中一个实例完成,这样说是否正确?
您应该区分函数宿主和函数执行。如果您共享 http 客户端,您的负载应该少于 100。参见 here