Nodejs,Cloud Firestore上传任务 - 身份验证错误:错误:套接字挂起

Posted

技术标签:

【中文标题】Nodejs,Cloud Firestore上传任务 - 身份验证错误:错误:套接字挂起【英文标题】:Nodejs, Cloud Firestore Upload Tasks - Auth error:Error: socket hang up 【发布时间】:2018-03-19 03:19:55 【问题描述】:

我正在编写一个函数,该函数运行 API 调用并通过偏移量从一个巨大的数据库按顺序请求 JSON。解析 JSON 响应,然后将其中的后续数据上传到我们的 Cloud Firestore 服务器。

Nodejs(Node 6.11.3)和最新的 Firebase Admin SDK

信息按预期解析,并完美打印到控制台。但是,当数据尝试上传到我们的 Firestore 数据库时,控制台会收到以下错误消息:

身份验证错误:错误:套接字挂起

(node:846) UnhandledPromiseRejectionWarning: 未处理的承诺拒绝 (拒绝 id:-Number-):错误:从插件获取元数据失败 错误:套接字挂断

偶尔:

验证错误:错误:读取 ECONNRESET

forEach 函数从下载的 JSON 中收集项目并在上传到 Firestore 数据库之前处理数据。每个 JSON 最多有 1000 条数据(价值 1000 个文档)通过 forEach 函数传递。我知道如果函数在上传集完成之前重复,这可能是一个问题?

我是一名编码新手,并且了解此功能的控制流程并不是最好的。但是,我找不到有关控制台打印错误的任何信息。我可以找到大量关于套接字挂断的信息,但在 Auth 错误部分没有。

我使用生成的服务帐户 JSON 作为凭据来访问我们的数据库,该数据库使用 firebase-adminsdk 帐户。我们对数据库的读/写规则目前是开放的,允许任何访问(因为我们正在开发中,没有真正的用户)。

这是我的功能:

Firebase 初始化和偏移归零

 const admin = require('firebase-admin');
    var serviceAccount = require("JSON");
    admin.initializeApp(
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "URL"
    );
    var db = admin.firestore();
    var offset = 0;
    var failed = false;

运行函数并设置 HTTP 标头

var runFunction = function runFunction() 
    var https = require('https');
    var options = 
        host: 'website.com',
        path: (path including an offset and 1000 row specifier),
        method: 'GET',
        json: true,
        headers: 
            'content-type': 'application/json',
            'Authorization': 'Basic ' + new Buffer('username' + ':' + 'password').toString('base64')
        
    ;

运行 HTTP 请求并在我们尚未到达 API 响应的末尾时重新运行函数

if (failed === false) 
        var req = https.request(options, function (res) 
            var body = '';
            res.setEncoding('utf8');
            res.on('data', function (chunk) 
                body += chunk;
            );
            res.on('end', () => 
                console.log('Successfully processed HTTPS response');
                body = JSON.parse(body);
                if (body.hasOwnProperty('errors')) 
                    console.log('Body ->' + body)
                    console.log('API Call failed due to server error')
                    console.log('Function failed at ' + offset)
                    req.end();
                    return
                 else 
                    if (body.hasOwnProperty('result')) 
                        let result = body.result;
                        if (Object.keys(result).length === 0) 
                            console.log('Function has completed');
                            failed = true;
                            return;
                         else 
                            result.forEach(function (item) 
                                var docRef = db.collection('collection').doc(name);
                                console.log(name);
                                var upload = docRef.set(
                                    thing: data,
                                    thing2: data,
                                )
                            );
                            console.log('Finished offset ' + offset)
                            offset = offset + 1000;
                            failed = false;
                        
                        if (failed === false) 
                            console.log('Function will repeat with new offset');
                            console.log('offset = ' + offset);
                            req.end();
                            runFunction();
                         else 
                            console.log('Function will terminate');
                        
                    
                
            );
        );
        req.on('error', (err) => 
            console.log('Error -> ' + err)
            console.log('Function failed at ' + offset)
            console.log('Repeat from the given offset value or diagnose further')
            req.end();
        );
        req.end();
     else 
        req.end();
    
    ;
    runFunction();

任何帮助将不胜感激!

更新

我刚刚尝试更改一次提取的 JSON 行,然后使用该函数一次上传 - 从 1000 到 100。套接字挂起错误的频率较低,因此肯定是由于过载数据库。

理想情况下,如果每个 forEach 数组迭代在开始之前等待前一次迭代完成,那将是完美的。

更新 #2

我已经安装了 async 模块,并且我目前正在使用 async.eachSeries 函数一次执行一个文档上传。上传过程中的所有错误都会消失 - 但是该功能将花费大量时间才能完成(158,000 个文档大约需要 9 小时)。我更新的循环代码是这样的,并实现了一个计数器:

async.eachSeries(result, function (item, callback) 
    // result.forEach(function (item) 
    var docRef = db.collection('collection').doc(name);
    console.log(name);
    var upload = docRef.set(
      thing: data,
      thing2: data,
    ,  merge: true ).then(ref => 
        counter = counter + 1
        if (counter == result.length) 
            console.log('Finished offset ' + offset)
            offset = offset + 1000;
            console.log('Function will repeat with new offset')
            console.log('offset = ' + offset);
            failed = false;
            counter = 0
            req.end();
            runFunction();
        
        callback()
    );
);

另外,一段时间后数据库返回这个错误:

(node:16168) UnhandledPromiseRejectionWarning: UnhandledPromiseRejectionWarning: Unhandled Promise reject (rejection id: -Number-): Error: 数据存储操作超时,或者数据暂时不可用。

现在我的函数似乎花费了太长时间......而不是不够长。有没有人有任何关于如何让这个运行更快而不出现错误的建议?

【问题讨论】:

【参考方案1】:

作为此循环一部分的写入请求只是超出了 Firestore 的配额 - 因此服务器拒绝了其中的大部分。

为了解决这个问题,我将我的请求转换为一次上传 50 个左右的项目块,Promises 确认何时进入下一个块上传。

答案贴在这里 -> Iterate through an array in blocks of 50 items at a time in node.js,我的工作代码模板如下:

async function uploadData(dataArray) 
  try 
    const chunks = chunkArray(dataArray, 50);
    for (const [index, chunk] of chunks.entries()) 
      console.log(` --- Uploading $index + 1 chunk started ---`);
      await uploadDataChunk(chunk);
      console.log(`---Uploading $index + 1 chunk finished ---`);
    
   catch (error) 
    console.log(error)
    // Catch en error here
  


function uploadDataChunk(chunk) 
  return Promise.all(
    chunk.map((item) => new Promise((resolve, reject) => 
      setTimeout(
        () => 
          console.log(`Chunk item $item uploaded`);
          resolve();
        ,
        Math.floor(Math.random() * 500)
      );
    ))
  );


function chunkArray(array, chunkSize) 
  return Array.from(
     length: Math.ceil(array.length / chunkSize) ,
    (_, index) => array.slice(index * chunkSize, (index + 1) * chunkSize)
  );

将数据数组传递给uploadData - 使用uploadData(data);并将每个项目的上传代码发布到 chunk.map 函数中 setTimeout 块内的 uploadDataChunk 中(在 resolve() 行之前)。

【讨论】:

【参考方案2】:

我通过chaining the promises in the loop 解决了这个问题,每次等待时间为 50 毫秒。

function Wait() 
    return new Promise(r => setTimeout(r, 50))


function writeDataToFirestoreParentPhones(data) 
    let chain = Promise.resolve();
    for (let i = 0; i < data.length; ++i) 
        var docRef = db.collection('parent_phones').doc(data[i].kp_ID_for_Realm);
        chain = chain.then(()=> 
            var setAda = docRef.set(
                parent_id: data[i].kf_ParentID,
                contact_number: data[i].contact_number,
                contact_type: data[i].contact_type
            ).then(ref => 
                console.log(i + ' - Added parent_phones with ID: ', data[i].kp_ID_for_Realm);
            ).catch(function(error) 
                console.error("Error writing document: ", error);
            );
        )
        .then(Wait)
    

【讨论】:

您最好使用每个 firestore docSet 返回的各个 promise。我在其中解决了承诺,以便我的代码仅在所有前一个块都已上传时才传递到下一个上传块。无需在迭代之间等待。【参考方案3】:

对我来说,这是一个网络问题。

分批上传 180,000 个文档 10,000 份对我来说之前没有问题,今天使用公共的较慢的 wifi 连接,我收到了该错误。

切换回我的 4G 移动连接为我解决了问题。不确定这是否是速度问题 - 可能是安全问题 - 但我会接受这个假设。

【讨论】:

以上是关于Nodejs,Cloud Firestore上传任务 - 身份验证错误:错误:套接字挂起的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 Cloud Functions 将文件上传到 Cloud Storage 并使用 Firestore 控制对 Cloud Storage 的访问?

我应该将图片上传到 Cloud Firestore 还是 Firebase Storage?

为啥我的 Firebase 存储 URL 没有上传到 Google Cloud Firestore?

使用文档 ID 上传存储文件并传递 DownloadURL 时如何避免双重 Cloud Firestore 写入

使用nodejs从firestore文档中删除字段

使用 Flutter 的 FieldValue arrayUnion 和 Cloud FireStore