如何处理firebase触发器函数的执行顺序

时间:2017-09-16 19:12:09

标签: firebase firebase-realtime-database google-cloud-functions

我有一个firebase http函数,它将数据附加到firebase数据库列表。第二个函数配置为在更改时处理列表,然后更新某些摘要数据。在我的情况下,这些更新突然爆发。我正在使用node.js firebase函数。

查看firebase日志,我从空列表开始看到这个序列:

  1. 从http添加到列表 - 列表有1个元素
  2. 从http添加到列表 - 列表有2个元素
  3. 从http添加到列表 - 列表有3个元素
  4. 从http添加到列表 - 列表有4个元素
  5. 总结包含1个元素的列表
  6. 总结包含3个元素的列表
  7. 总结了包含4个元素的列表
  8. 总结了包含2个元素的列表
  9. 我的问题是摘要只包含2个元素而不是4个。

    看起来总结触发器函数是并行调用而不是顺序调用,因此当几个触发器紧密接近时,最后一个触发器可以是触发而不是最后一个触发器之一。

    可以使用哪些方法来确保汇总计算具有“所有数据”并且运行较慢的先前汇总计算不会覆盖后者?可以将firebase函数触发器序列化以按照它们的启动顺序执行吗?

    理想情况下,我希望避免在突发进入时计算摘要N次,以便将来可以在短时间内“安排”摘要然后取消并在新事件发生时重新安排的解决方案很好。

2 个答案:

答案 0 :(得分:2)

对于来自多个客户端或调用的事件的交付顺序,绝对不能保证。事实上,你甚至很难定义事件的时间,因为在客户提出请求的那一刻,以及你的函数内部最终工作的那一刻,有很多可变的移动部分。为该客户完成。

您可以做的最好的事情是假设多个客户端都有效地向您的函数发送无序请求,并使用数据库事务来防止它们所做的写入发生任何类型的冲突。

如果您绝对必须序列化事物,那么您需要让其他程序或代理定义正确的序列并序列化所有工作,确保所有写入都以可预测的顺序发生。

如果您想阅读关于为什么排序困难的扩展讨论,read this article(云函数建立在pubsub之上)。

答案 1 :(得分:0)

我的解决方法是存储一个admin.database.ServerValue.TIMESTAMP,其中列表添加并在结果计算器中验证它是否为最新的时间戳生成了结果。如果没有,它会再次尝试。在大多数情况下,它不需要重新计算摘要,因为我的输入源通常是零星的单个列表添加而不是集总添加。我将它实现为一个返回Promise的函数,如果需要重新计算,它会调用自身。这是序列:

  1. 阅读当前列表和时间戳
  2. 计算摘要结果并存储
  3. 再次阅读时间戳
  4. 如果时间戳不同,请转到1,否则完成
  5. 以下是代码:

    /// return a Promise that new summary and detail results will be posted
    function updateResults(regattaId, lapdataTS, depth) {
      if (depth > 10) {
        return Promise.reject("Too many recomputes");
      }
      return admin.database().ref('/eventdata/'+regattaId).once('value')
      .then(function (snapshot) {
        const rawdata = snapshot.val(); 
    
        if (rawdata.lapdataTS === lapdataTS) {
            // console.log("already computed");
            return Promise.resolve();
        }
        lapdataTS = rawdata.lapdataTS ? rawdata.lapdataTS : null;
        const results = regattaCalc.computeResults(rawdata);
    
        var updates = {};
        updates['results/' + regattaId] = results;
        updates['summary/' + regattaId] = results.regattaInfo;
        return admin.database().ref().update(updates);
      }).then(function () {
        // read last TS and see if it matches our summary
        return admin.database().ref('/eventdata/'+regattaId+'/lapdataTS').once('value');
      }).then(function (snapshot) {
       if (snapshot.val() === lapdataTS) {  
           return Promise.resolve();
       } else {
           //console.log("Need to calc again");
           return updateResults(regattaId, lapdataTS, depth++);
       }
      }).catch((reason) => {
        console.log("Error generating summary: " + reason);
        return Promise.reject(reason);
      });
    }
    
    exports.compupteResults = functions.database.ref('/eventdata/{regattaId}').onWrite(event => {
    return updateResults(regattaId,null,0);
     });