1

考虑以下:

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 事务的更新函数的用户对象与 userNotificationRef 事务的更新函数返回的数据相同。

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

4

1 回答 1

3

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

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

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

最重要的是,在撰写本文时,SDK 中仍然存在一个错误,错误的基值很少会“卡住”,并且事务会不断重试(maxretry经常失败),直到您重新启动该过程。

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

于 2016-08-06T00:42:30.733 回答