【问题标题】:Firebase transactions bugFirebase 事务错误
【发布时间】:2016-08-06 08:42:30
【问题描述】:

考虑以下几点:

function useCredits(userId, amount){
    var userRef = firebase.database().ref().child('users').child(userId);
    userRef.transaction(function(user) {

        if (!user){
            return user;
        }
        user.credits -= amount;
        return user;


    }, NOOP, false);
}

function notifyUser(userId, message){

    var notificationId = Math.random();
    var userNotificationRef = firebase.database().ref().child('users').child(userId).child('notifications').child(notificationId);
    userNotificationRef.transaction(function(notification) {

        return message;

    }, NOOP, false);
}

这些是从同一个节点 js 进程中调用的。

一个用户看起来像这样:

{
  "name": 'Alex',
  "age": 22,
  "credits": 100,
  "notifications": {
    "1": "notification 1",
    "2": "notification 2"
  }
}

当我运行压力测试时,我注意到有时传递给 userRef 事务更新函数的用户对象不是完整用户,它只是以下内容:

{

  "notifications": {
    "1": "notification 1",
    "2": "notification 2"
  }
}

这显然会导致错误,因为 user.credits 不存在。

怀疑传递给userRef事务的update函数的用户对象与userNotificationRef事务的update函数返回的数据相同。

为什么会这样?如果我在用户父位置上运行两个事务,这个问题就会消失,但这是一个不太理想的解决方案,因为我会有效地锁定并读取整个用户对象,这在添加一次写入通知时是多余的。

【问题讨论】:

    标签: javascript firebase firebase-realtime-database firebase-queue


    【解决方案1】:

    根据我的经验,您不能依赖传递给事务更新函数的初始值。即使数据已填充到数据存储中,也可能使用 null、部分值或过时的旧值(以防本地更新进行中)调用该函数。这通常不是问题,只要您在编写函数时采取防御措施(而且您应该这样做!),因为虚假更新将被拒绝并重试事务。

    但请注意:如果您因为数据没有意义而中止交易(通过返回undefined),那么它不会针对服务器进行检查并且不会重试。出于这个原因,我建议永远不要中止交易。我建立了一个monkey patch 来透明地应用这个修复(和其他);它仅适用于浏览器,但可以轻松适应 Node。

    您可以做的另一件事是在事务之前在同一个 ref 上插入一个 on('value') 调用,并使其保持活动状态直到事务完成。这通常会导致事务在第一次尝试时在正确的数据上运行,不会过多影响带宽(因为无论如何都需要传输当前值),并且会稍微增加本地延迟如果您设置了applyLocally 或默认为true。我在我的NodeFire 库中do this,以及许多其他优化和调整。

    除此之外,在撰写本文时,SDK 中仍然存在一个错误,错误的基值很少会“卡住”,并且事务会不断重试(经常以maxretry 失败),直到您重启进程。

    祝你好运!我仍然在我的服务器中使用事务,可以轻松地重试失败并且我有多个进程正在运行,但是已经放弃在客户端上使用它们——它们太不可靠了。在我看来,重新设计数据结构通常会更好,这样就不需要事务了。

    【讨论】:

    • 我不知道交易有这样的问题...... +1。您如何建议在不需要事务的情况下重新设计这种“增加值”操作?
    • 非常感谢您的详细回复 - 明白了。交易似乎有些破裂——在我看来,客户不必担心所有这些考虑因素。您如何建议在不使用 firebase 交易的情况下对银行账户余额进行建模?如果没有一种强大的方法来使用事务来处理这个问题,那么我觉得这是一个严重的限制。另一个问题是能够以事务方式更新某些值并同时以原子方式更新其他位置。
    • 这都是来之不易的经历,重点是hard。只要您牢记上述注意事项,对于简单的增量,事务可能就可以了。另一种方法是将“+N”条目推送到日志并让服务器通过事务定期汇总它们,这应该更健壮且最终一致。客户端可以读取最后的聚合值并记录以即时计算当前值。深度原子更新是可能的,但不能是事务性的——同样,基于日志的设计是你的朋友。
    • 非常感谢您的 cmets。
    猜你喜欢
    • 1970-01-01
    • 2019-02-22
    • 2020-09-19
    • 2017-11-13
    • 2021-11-14
    • 2018-10-11
    • 2016-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多