【问题标题】:Avoding infinite loop in Firebase with child_added/child_changed避免在 Firebase 中添加子项/更改子项的无限循环
【发布时间】:2014-05-16 18:47:21
【问题描述】:

我正在尝试创建一个工作进程来侦听 Firebase 以了解对特定集合的添加/更改。在任何一种情况下,都应该运行一个进程,该进程最终会根据它在那里找到的内容将一些额外的数据写回集合。我遇到的问题是这会导致无限循环,因为 child_changed 将再次触发回调,因为我正在更改它正在侦听的同一棵树中的模型。此外,在第一次通过 child_add 触发 child_changed 时存在初始重复工作。

我想出的唯一解决方案是以某种方式将数据放入 Firebase,表明不应发生更新(is_dirty: false,或类似的东西)。我对那个解决方案不是特别满意,因为这要求客户端在添加/更改应该触发服务器处理的内容时参与翻转标志或其他内容。我希望他们不要那么耦合。我当然喜欢维护某种缓存集合以比较更改的想法,因为这会复制大量数据。

我还考虑在更新之前以某种方式使用off(之后将其重新打开),但我担心这会导致我错过可能在那个时间窗口内发生的更改。也许这是交易的情况?

这里的最佳做法是什么?目前,我的代码如下所示:

collectionRef.on('child_added', processModel);
collectionRef.on('child_changed', processModel);

var processModel = function (modelSnapshot) {
  // do some stuff that updates model
}

【问题讨论】:

    标签: javascript firebase


    【解决方案1】:

    您的主进程和工作进程(默认情况下)绑定非常松散 - 仅通过一个修改数据而另一个被告知数据危险已更改的事实链接。

    添加队列或信号量是一种更紧密地绑定它们的方法。

    但是,我更喜欢的模型通常是将它们绑定得更松...

    不要考虑事件,而要考虑数据的状态。

    设计您的工作进程以获取脏数据库并使其干净。然后将 'on update' 调用视为一种优化 - 有效地为您的工作进程提供关于数据库的哪个部分可能需要清理的“线索”。

    使用这种模式意味着没有“无限更新”问题,因为一旦清理,就不会执行额外的更新,而且它的可扩展性非常强 - 您可以让多个工作进程在数据库的不同部分工作,或者以不同的方式清理不同的元素。

    不利的一面是,必须将数据结构化以在“状态”下工作,并且使用数据的程序必须在设计时考虑到它们读取的数据可能是脏的。虽然如果这很重要,我会让客户端读取数据以检查它是否脏并在继续之前触发清理......

    【讨论】:

    • 我认为这更接近我希望采用的方法。我对排队的冒险提出了一些以前甚至不是问题的复杂性。但是,我不确定我是否理解您所说的“将 'on update' 调用视为一种优化 - 有效地为您的工作进程提供关于数据库的哪个部分可能需要清理的'线索'”。你会详细说明吗?您是否实质上是建议客户保存到暂存区,而工人清理并保存到最终集合?
    • 如果没有关于您的实际数据的更多信息,很难更具体。我的想法是最初设计更清洁的代码来修复整个数据库(就像按计划遍历整个数据库一样),然后在更新时对其进行细化/优化,以根据更新清理它需要的位。你能举一个数据的例子吗?
    • 基金有很多投资组合有很多头寸。用户可以添加新的投资组合(至少 1 个头寸)、在现有投资组合下添加新头寸、更改头寸的代码或更改投资组合的日期。任何这些都将要求工作人员从第三方获取市场数据,进行一些计算,并将结果附加到每个位置,以便显示。客户端在此期间没有此性能数据是可以的。工作人员还按计划更新现有模型的这些指标。信息够了吗?
    • @neverfox 有几个想法 - 如果您在单独的记录中拥有市场数据,您可以简单地检查它的存在以查看是否需要添加。此外 - 只有在数据实际发生变化时才会触发 on value 事件 - 因此,如果市场数据在主记录中,则只有在实际发生变化时才会触发事件......这本身可以避免您担心的无限循环.
    • 抱歉,不能存储市场数据。在任何情况下,仍然需要根据投资组合/头寸属性的变化以不同方式处理该数据,即使市场数据本身没有变化(不同的相关日期或符号等)。当然可以在客户端执行这些计算,但性能是不可接受的。到目前为止,我有一个正在完成工作的工作队列。我只是想进一步了解您建议的替代方案。
    【解决方案2】:

    [在不了解您的用例的情况下,回答这个问题有点困难,但我会提供一些一般性的思考。]

    请务必记住,Firebase 会同步数据,而不是“事件”。因此,每次您重新启动工作进程时,它都会为所有现有数据获取 child_added 事件。

    此外,如果您的工作进程已经关闭了一个小时并且用户对模型中的项目进行了一系列更改,那么当您的工作进程再次启动时,它仍然会得到一堆 child_added 事件,而不是离线更改的单个 child_changed 事件。

    所以你通常想做两件事之一:

    1. 在 Firebase 中存储一个“工作队列”,让您的工作进程从队列中抓取项目,处理它们,然后丢弃它们(或将它们标记为已完成或将它们移动到其他位置或其他任何地方)。

    2. 在每个模型对象中存储一个“状态”,指示它所处的状态以指示需要进行什么处理(类似于您的肮脏想法)。用户可能将一个项目添加为“初始”,您的工作进程将拾取它,进行处理,然后以“已处理”或类似的状态将其写回。如果客户端进行了修改,它会将其标记为“脏”,并且您的工作进程将再次进行修改并将状态更新为“已处理”

    如果您采用其中一种方法,您的工作进程应该很容易知道它需要处理哪些项目,并且它应该优雅地处理重新启动/停机等,正确地从中断的地方“接起”。

    【讨论】:

    • 感谢您的回复。回复:工人开始时会发生什么,这是故意的。工作人员应该保证数据库包含它提供的信息并且它是最新的,所以如果它发生故障并重新启动,我希望它不做任何假设并处理一切安全。也就是说,尽管我最初有疑虑,但我认为我需要实施其中一个计划作为更好的选择。队列的一个特殊优势在于我可以存储对相关位置的引用。
    • 另一个选项(我不推荐)是在写入期间设置一个标志(即“localUpdate = true; ref.set('some data you want to ignore.'); localUpdate = false;") 然后在您的事件处理程序中,检查 localUpdate 如果它是真的,忽略该事件。但这依赖于 .set() 同步引发事件这一事实,我们可能会在 API 的未来版本中进行更改。
    • 这是一个例子work queue。这些非常有用。
    • 我看到了那个,@Kato,谢谢。不过我最终还是选择了firework
    猜你喜欢
    • 2021-03-07
    • 1970-01-01
    • 2017-12-15
    • 1970-01-01
    • 1970-01-01
    • 2020-10-16
    • 2021-05-17
    • 2011-12-10
    • 1970-01-01
    相关资源
    最近更新 更多