我在使用 Google Cloud 功能时遇到问题 - 出现 ECONNRESET 错误

问题描述 投票:0回答:1

我创建了一个函数,它接受存储在 Firebase 存储中的图像的 URL 字符串数组,该函数成功地适用于总下载量小于 1GB 的实例。

但是,随着数量的增加,我看到的最后一个控制台日志是:

“所有图像均传输至 zip”

然后好几秒钟什么也没有发生,我得到:

函数执行花费了 141568 毫秒,完成状态:“崩溃”

然后:

**错误:读取 ECONNRESET 在 TLSWrap.onStreamRead (节点:内部/stream_base_commons:217:20) 在 TLSWrap.callbackTrampoline (节点:内部/async_hooks:130:17)**

我在互联网上寻找解决方案,但我只能找到 2017 年的解决方案,人们建议 google-cloud-node 库对套接字的处理以及云功能环境中的默认套接字超时已得到修复。

这是我的云功能:

const functions = require('firebase-functions');
const fetch = require('node-fetch');
const archiver = require('archiver');
const fs = require('fs');
const path = require('path');
const os = require('os');
const admin = require('firebase-admin');
const { Storage } = require('@google-cloud/storage');

admin.initializeApp({
 credential: admin.credential.applicationDefault(),
 storageBucket: process.env.FIREBASE_STORAGE_BUCKET
});

const runtimeOpts = {
 timeoutSeconds: 300,
 memory: '8GB'
};

exports.batchDownload = functions
 .runWith(runtimeOpts)
 .https.onRequest(async (req, res) => {
    console.log('Function started');
    res.set('Access-Control-Allow-Origin', '*');
    res.set('Access-Control-Allow-Methods', 'POST');
    res.set('Access-Control-Allow-Headers', 'Content-Type');

    if (req.method === 'OPTIONS') {
      console.log('OPTIONS request received');
      res.status(204).send('');
      return;
    }

    const imageUrls = req.body.imageUrls;
    const inspectionId = req.body.id;

    if (!Array.isArray(imageUrls)) {
      console.log('Invalid request format');
      res.status(400).send('Invalid request: incorrect data format');
      return;
    }

    const tempDir = path.join(os.tmpdir(), 'images');
    const zipPath = path.join(os.tmpdir(), 'images.zip');

    if (!fs.existsSync(tempDir)) {
      console.log('Creating temporary directory');
      fs.mkdirSync(tempDir);
    }

    try {
      // Create zip archive
      const output = fs.createWriteStream(zipPath);
      const archive = archiver('zip', {
        zlib: { level: 9 },
      });

      archive.pipe(output);

      // Stream images directly to the zip file
      await Promise.all(imageUrls.map(async (url, index) => {
        console.log(`Streaming image from ${url}`);
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`Failed to fetch image from ${url}`);
        }
        const stream = response.body;
        const filePath = `image${index}.jpg`;
        archive.append(stream, { name: filePath });
      }));

      console.log('All images streamed to zip');

      // Finalize the archive
      await archive.finalize();

      // Upload zip file to Cloud Storage
      const storage = new Storage();
      const bucket = storage.bucket(process.env.FIREBASE_STORAGE_BUCKET);
      const file = bucket.file(`inspections/${inspectionId}/images.zip`);
      const stream = file.createWriteStream({
        metadata: {
          contentType: 'application/zip',
        },
      });

      await new Promise((resolve, reject) => {
        stream.on('error', reject);
        stream.on('finish', resolve);
        fs.createReadStream(zipPath).pipe(stream);
      });

      console.log('Zip file uploaded to Cloud Storage');

      // Generate download URL
      const config = {
        action: 'read',
        expires: Date.now() + 60 * 60 * 1000, // Set the expiration to 1 hour from now
      };
      const [url] = await file.getSignedUrl(config);
      console.log('Generated download URL:', url);

      // Send response with download URL
      res.status(200).send({ downloadUrl: url });
    } catch (error) {
      console.error('Error during batch download:', error);
      res.status(500).send('Error during batch download');
    } finally {
      // Cleanup temporary files
      fs.rmdirSync(tempDir, { recursive: true });
      fs.unlinkSync(zipPath);
    }
 });

这是我的前端异步函数:

 const downloadAllImages = async () => {
   if (imagesToDownload.length < 1) {
      return;
   }
  
   const imageUrls = imagesToDownload.map(image => image.downloadURL);
   const zipFilePath = `inspections/${inspection.id}/images.zip`;
  
   try {
      const fileExists = await checkIfFileExists(zipFilePath);
      if (!fileExists) {
        // If the file does not exist, call the Cloud Function to generate it
        setIsDownloading(true);
        const response = await fetch(CLOUD_FUNCTION_URL, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ imageUrls, id: inspection.id }),
        });
  
        if (!response.ok) {
          throw new Error(`Failed to initiate image download: ${response.statusText}`);
        }
  
        const data = await response.json();
        console.log("reponse", data)
        const downloadUrl = data.downloadUrl;
  
        const a = document.createElement('a');
        a.href = downloadUrl;
        a.download = 'images.zip';
        a.click();
  
        setShowDownloadAllImagesModal(false);
        showNotification("Images Downloaded!");
        setIsDownloading(false);
      } else {
        const storage = getStorage();
        const zipRef = ref(storage, zipFilePath);
  
        getDownloadURL(zipRef)
        .then((url) => {
          const a = document.createElement('a');
          a.href = url;
          a.download = 'images.zip';
          a.click();
        })
        .catch((error) => {
          console.error('Error downloading file:', error);
          showNotification(`Error downloading file: ${error}`);
        });
        setShowDownloadAllImagesModal(false);
        showNotification("Images Downloaded!");
        setIsDownloading(false);
      }
   } catch (error) {
      console.error(`Error checking file existence or initiating image download: ${error}`);
      setShowDownloadAllImagesModal(false);
      showNotification(`${error}`);
      setIsDownloading(false);
   }
  };

我已将其设置为第一代云功能,它可以在总计小于 1GB 的实例中运行,但如果我尝试 3GB - 9GB 的图像文件,我会遇到 ECONNRESET 问题。

有人可以提供任何见解或建议进一步排除故障吗?

感谢您花时间阅读本文。

javascript node.js google-cloud-functions backend
1个回答
0
投票

Google 云功能曾经宣传过具有 256mb 临时存储的硬限制,尽管我现在找不到任何具体参考。我想您正在使该功能崩溃,因为 /tmp 目录超出了允许的最大大小。

考虑改变你的方法,你可以使用 google-cloud/storage 包将下载直接流式传输到谷歌云存储。或者只是通过对 url 数组进行分块,将您的下载分块为更小、更易于管理的批次。

© www.soinside.com 2019 - 2024. All rights reserved.