Использование запроса узла или axios с потоками для загрузки и распаковки файла без обработки обратного давления, как ожидалось? - PullRequest
5 голосов
/ 25 сентября 2019

У нас есть большой файл размером около 6 ГБ, который распакован до размера 64 ГБ (образ ОС), который нам нужно загрузить с помощью http.Мы используем либо библиотеку запросов узла, либо axios.Файл загружается и распаковывается (передается по каналу) на лету, используя следующий код:

 const downloadUsingHttp = (downloadUrl, destinationPath) => {enter code here
      return new Promise((resolve, reject) => {
        const unpackedPathWriteStream = fs.createWriteStream(destinationPath);

        let totalDownloadSize = 64023257088;
        let downloadedSize = 0;
        let lastProgressSent = 0;

        axios({
          method: 'get',
          url: downloadUrl,
          responseType: 'stream',
          auth: {
            username: 'user',
            password: 'pass'
          },
            withCredentials: true
         }).then(function (response) {
            response.data
              .on('data', chunk => {
                if (totalDownloadSize === 0) {
                  return;
                }
                downloadedSize += chunk.length;
                const progress = Math.floor((downloadedSize / totalDownloadSize) * 100);

                if (progress % 5 !== 0) {
                  return;
                }

                if (lastProgressSent === progress) {
                  return;
                }

                lastProgressSent = progress;
                console.log('Copy progress ', progress + ' %')
              })
              .pipe(zlib.createUnzip())
              .pipe(unpackedPathWriteStream)

        }).catch((err) => {
           console.log(err.message)
        });

        unpackedPathWriteStream
          .on('error', err => {
            console.log(err);
            reject(err);
          }).on('end', () => {
            resolve();
          })
   })
};

downloadUsingHttp(
  'https://example.com/storage/file.raw.gz',
  '/data/downloaded-and-unziped.raw'
);

На компьютере, на котором запущен этот код, имеется 2 ГБ ОЗУ.Когда этот код запускается, возникает проблема, связанная с тем, что машине не хватает оперативной памяти, при прогрессе около 15% и сбое приложения узла.Иногда даже вся машина перестает отвечать на запросы и требует перезагрузки.

Таким образом, похоже, что обработка обратного давления , реализованная через .pipe () для потоков, в этом случае не работает.Например, если не загружать файл через http (с помощью библиотеки запросов или axios), но делать это с читаемыми и записываемыми потоками, выполнять те же операции копирования и распаковки на лету с использованием метода pipe и не происходит исчерпание памяти.

Также важно упомянуть, что эта проблема возникает только при выполнении загрузки по http в локальной сети (локальная среда разработки).

Любая помощь будет оценена.

Обновление

Мы пытались дросселировать поток до 100 КБ / с, и, похоже, он работает в условиях, когда не было увеличенного использования памяти RAM.При изменении на 1 МБ / с использование увеличивается и в конечном итоге приложение тормозит.Мы использовали библиотеку stream-throttle, чтобы попробовать это.

1 Ответ

0 голосов
/ 25 сентября 2019

У меня нет большого опыта работы с конвейерами, но как насчет загрузки файла порциями и подачи их в конвейер по одному.затем загрузка следующего куска.Таким образом, канал будет иметь дело только с несколькими МБ данных одновременно.

Я представляю что-то вроде этого:

const downloadUsingHttp = (downloadUrl, destinationPath, chunkSize = 10<<20) => {
  const writeStream = fs.createWriteStream(destinationPath);
  const unzip = zlib.createUnzip();

  const auth = {
    username: 'user',
    password: 'pass'
  };

  const nextChunk = () => axios({
      method: 'get',
      url: downloadUrl,
      responseType: 'stream',
      auth: auth,
      withCredentials: true,
      headers: {
        Range: `bytes=${offset}-${(offset += chunkSize)}`
      }
    }).then(downThePipe);

  const downThePipe = response => {
    console.log("progress %i%%   ( %i / %i bytes )", offset / length * 100, offset, length);
    response.data.pipe(unzip).pipe(writeStream);

    return offset < length ? nextChunk() : null;
  };

  let offset = 0, length;
  return axios({
    method: "HEAD",
    url: downloadUrl,
    auth: auth,
    withCredentials: true,
  }).then(response => {
    length = response.headers["Content-Length"];
    return nextChunk();
  });
};



downloadUsingHttp(
  'https://example.com/storage/file.raw.gz',
  '/data/downloaded-and-unziped.raw'
);

Возможно, если загрузка все еще слишком быстраявы бы хотели отложить загрузку nextChunk() до окончания работы pipe().Но опять же, я давно работал с этими трубами.

...