App Engine中缺少全局变量值Node.js

问题描述

我在App Engine上部署了一个Node.js服务,该服务使用Dialogflow履行库。情况是这样的:我有一个异步函数,该函数使用Secret Manager检索凭据,并使用该信息调用调用API的API,该URL实例和令牌返回。这是服务器到服务器的身份验证(OAuth),因此所有访问它的用户都相同。我在全局变量中设置这些值,如下所示:

let globalUser = "";
let globalPass = "";
...

async function credentials() {
    const credentials = await secretsInstance.getCredentials();
    const parsedCredentials = JSON.parse(credentials);
    const user = parsedCredentials.user;
    const pass = parsedCredentials.pass;
    //setting the values to the global variables
    globalUser = user;
    globalPass = pass;
    
    //call the authentication API - in the callback I set other global variables
    await authApiInstance.authenticate(user,pass,callback);
}

调用回调函数后,我将实例url和令牌设置为全局变量。 令牌每20分钟过期一次,因此我需要对其进行更新。为此,我调用了setInterval函数,在其中我调用authApiInstance.authenticate(...)

这里的问题是,当接收到来自Dialogflow的POST请求时,我需要调用一个需要该URL的API,该URL在此阶段首次为空,因此会抛出ECONNREFUSED。然后,如果我其他时间呼叫服务器,则会设置该变量。

GCP中的日志如下:

2020-08-14 23:29:49.078 BRT
"Calling the loadQuestions API

2020-08-14 23:29:49.078 BRT
"The url is: /services/…

2020-08-14 23:29:49.091 BRT
"CATCH: Error: connect ECONNREFUSED 127.0.0.1:80"

2020-08-14 23:29:49.268 BRT
dialogflowGatewayProdxjmztxaet4d8Function execution took 764 ms,finished with status code: 
200

2020-08-14 23:29:49.278 BRT
{ message_id: '39045207393',status: 200 }

2020-08-14 23:29:49.289 BRT
"Credentials ok"

2020-08-14 23:29:49.976 BRT
"Url set"

可以看出,凭据和URL是在调用API后设置的,因此它没有可成功进行调用的URL。

我可以在POST中调用函数,每次有一个保证它永远存在的请求时,但是性能会丢失,尤其是处理必须快速的聊天机器人。

我还尝试了预热方法,从理论上讲,该方法将在部署和更改实例时调用(但不能像docs那样调用它):

app.get('/_ah/warmup',(req,res) => {
   credentials();
});

我该如何处理?我对Node.js和服务器世界还很陌生。

谢谢

解决方法

credentials();本身。无需做快递。我的问题是共享凭据上的竞赛条件。

一个粗略的例子,假设事件循环在队列中只有这些脚本: 假设您有2个并发用户A和B。一个请求并发现凭据到期,这又请求新的凭据。从A请求返回凭据之前的B请求,后者又请求另一个凭据。基于节点eventloop,A然后获取credential_A,B将获取凭据B。如果您的第三方仅允许单个凭据,则A会从api调用中获取错误。

因此,方法是将与凭证相关的任务转发到一个管理凭证的模块。后台任务或请求(获取令牌将在请求时过期)将面临相同的比赛问题。由于节点没有线程上下文,因此很简单。

let credential = {}

let isUpdating = false;

const _updateCrediental = (newCrediential){ 

  //map here
  
  
}

const _getCredential = async()=> {

  try{
  
    if(!updating){
    
      updating = true;
      
      const newCrediential = await apiCall();
      
      updateCrediential(newCrediential);
      
      
      updating = false;
      
      return credential;
      
    }else{
    
      return false;
    }
    
  }catch(err){
    
    throw err;
  }
 }
 
 
 export.getCredential = ()=>{
   
      if(credentialIsValid()){

        return credential;
        
      }
      return __getCredential();
   }
  /// check the return if it promise type then waaait for it if its false then wait for certain time and check again.
 

对此的一种改进是使用事件代替超时。

我本人更喜欢使用数据库,并且您可能还希望记录凭证生成。大多数数据库承诺进行某种类型的事务处理或锁定。 (感觉更安全)