【问题标题】:How can i debounce the execution of a firebase cloud function correctly我怎样才能正确地去抖动firebase云功能的执行
【发布时间】:2020-02-26 20:57:29
【问题描述】:

我有一个 Firebase 云函数,它根据 Firebase 的 documentation 中提供的示例监控我的实时数据库的更改。

我的函数工作正常,每次更改都按原样执行。

话虽如此,并且根据 Firebase 的建议:

• 去抖动 - 当监听 Cloud Firestore 中的实时更改时,此解决方案可能会触发多个更改。如果这些更改触发的事件超出您的预期,请手动消除 Cloud Firestore 事件的抖动。

我想这样做。

谁能提供一个好的方法?

如果我们根据 Firebase 的例子来看这个函数:

exports.onUserStatusChanged = functions.database.ref('/status/{uid}').onUpdate(
            async (change, context) => {

  // Get the data written to Realtime Database
  const eventStatus = change.after.val();

  // Create a reference to the corresponding Firestore document
  const userStatusFirestoreRef = firestore.doc(`status/${context.params.uid}`);

  // re-read the current data and compare the timestamps.

  const statusSnapshot = await change.after.ref.once('value');
  const status = statusSnapshot.val();

  // If the current timestamp for this data is newer than
  // the data that triggered this event, we exit this function.

  if (status.last_changed > eventStatus.last_changed) {
    return null;
  }

  // Otherwise, we convert the last_changed field to a Date

  eventStatus.last_changed = new Date(eventStatus.last_changed);

  // write it to Firestore

  userStatusFirestoreRef.get().then((user: any) => {
    user.forEach((result: any) => {       
      result.ref.set(eventStatus, { merge: true })
    });
  });
  return;
});

我应该如何尝试去抖动它的执行?

我可以尝试去抖动.onUpdate() 事件吗?

我最初认为以下就足够了:

functions.database.ref('/status/{uid}').onUpdate(
  debounce(async(change:any, context:any) => {
    ...
  }, 10000, {
    leading: true,
    trailing: false
  })
);

但是,感谢@doug-stevenson 指出尝试以这种方式对 onUpdate 事件进行去抖动将不起作用,原因如下:

“这是行不通的,因为函数的每次调用都可能发生在完全不同的服务器实例中,没有共享上下文。”

【问题讨论】:

    标签: javascript firebase google-cloud-functions debounce


    【解决方案1】:

    这里有两种方法。 (如果您对延迟油门没问题——也就是运行“至少一次”而不是“恰好一次”去抖动,则方法更简单)。

    “至少一次”

    这可以通过一个任务调度器(例如,Google Cloud Tasks)来完成。您需要提出一个任务命名约定,让您可以对冗余任务进行重复数据删除。例如,以下是您每分钟最多执行一次并在任务挂起时忽略调用的方式。

    // round up to the nearest minute
    const scheduleTimeUnixMinutes = Math.ceil(new Date().getTime() / 1000 / 60);
    const taskName = id + scheduleTimeUnixMinutes.toString();
    const taskPath = client.taskPath(project, location, queue, taskName);
    
    // if there's already a task scheduled for the next minute, we have nothing
    // to do.  Google's client library throws an error if the task does not exist.
    try {
      await client.getTask({ name: taskPath });
      return;
    } catch (e) {
      // NOT_FOUND === 5.  If the error code is anything else, bail.
      if (e.code !== 5) {
        throw e;
      }
    }
    
    // TODO: create task here
    

    “恰好一次”

    这需要一个任务调度器和一些机制来让被调用的任务无操作。您需要在每次调用 firestore 函数时将一个全新的任务加入队列,而不是选择性地将任务加入队列。

    将任务排入队列后,您需要将latestTaskId 存储在持久存储中。如果另一个任务入队,它将覆盖该字段。然后,当每个任务执行时,它可以检查它是否与latestTaskId 匹配。如果没有,它可以早点返回,什么都不做。

    【讨论】:

      【解决方案2】:

      由于每个事件可能会多次传递,因此您必须跟踪context.eventId 中提供的事件 ID。如果您看到重复的事件,您就知道它正在重复。

      有许多策略可以做到这一点,而且没有一种正确的方法可以做到这一点。您可以将处理后的 ID 存储在数据库或其他一些持久性存储中,但不能只将其存储在内存中,因为每个函数调用都可以彼此完全隔离。

      另请阅读“幂等性”,因为这是函数的属性,每次调用的行为方式都相同。

      https://firebase.google.com/docs/functions/tips#write_idempotent_functions

      https://cloud.google.com/blog/products/serverless/cloud-functions-pro-tips-building-idempotent-functions

      【讨论】:

      • 当你说重复的context.eventId构成重复事件时,究竟重复了什么?执行到相同的解析路径,例如 ref('/status/UID12345'),更新/更改的数据,或者两者都需要为真(相同的解析路径,相同的尝试数据更改)才能重复事件 ID?跨度>
      • 传递给函数的整个事件。个人与它无关。
      • 对不起,那条评论应该说“个人路径与它无关”。
      • 好的,所以如果我理解正确的话,我需要考虑到一个事件可能重复发生,这可以通过匹配 eventId 来检测,并且因此,在我的情况下,id 需要确保我仅针对尚未“处理”的唯一 eventId 去抖动执行?通过“已处理”,我引用了您的链接视频教程,而我可以将 eventid 与我的数据一起存储,作为跟踪事件是否已完成的一种方式。
      • 是的,您需要忽略之前记录成功处理事件的调用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多