FireBase - maintain/guarantee 数据一致性

FireBase - maintain/guarantee data consistency

我正在尝试了解以下情况的正确方法:

多人游戏,每局 only 两名玩家。每个game/match将完全随机化

让我们假设 5 个用户 "logs" 同时进入我的应用程序,每个用户 "searching" 匹配一次。每个用户持有一个名为 opponent 的 属性 等于对手 uniqueID(初始值等于 ""。到目前为止一切顺利。

假设用户 1 与用户 3 匹配。用户 1 会将自己的 oppoent 值更新给用户 3 uniqueID 并将对用户 3 执行相同的操作

问题

1) 如果在同一时刻,用户 2 尝试对用户 3 进行同样的操作怎么办? 2) 如果在同一时刻,用户 3 试图对用户 4 这样做怎么办?

要点

是否可以 "lock" 用户值?或者一旦他们改变就冻结他们?我走错路了吗?

我正在考虑使用 Security RulesValidation 来创建一致性,但我可能选择了错误的技术 (FireBase)。有什么想法吗?

编辑

我已经尝试过的安全规则,由于某种原因仍然启用第三个设备更改 "already changed opponent" 值。

{
    "rules": {
        ".read": true,
         ".write": true,
      "Users" : 
      {
      "$uid" :  { 
        "opponent" :
        {
          ".write" : "data.val() == 'empty' || data.val() == null",
          ".validate": "data.val() == null || data.val() == 'empty' || newData.parent().parent().child(newData.val())
                    .child('opponent').val() == $uid"
        }
        ,".indexOn": "state"
      }
      }

    }
}

你可以用 Firebase security rules 验证很多东西。

例如,您可以说只有当用户当前没有对手时才能写入对手:

"users": {
  "$uid": {
    "opponent: {
      ".write": "!data.exists()"
    }
  }
}

通过此操作和以下操作:

ref.child('users').child(auth.uid).child('opponent').set('uid:1234');
ref.child('users').child(auth.uid).child('opponent').set('uid:2345');

第二个 set() 操作将失败,因为 opponent 属性 此时已经有一个值。

您可以扩展它以验证对手必须相互引用:

"users": {
  "$uid": {
    "opponent: {
      ".write": "!data.exists()"
      ".validate": "newData.parent().parent().child(newData.val())
                    .child('opponent').val() == $uid"
    }
  }
}
  1. 从正在写入的 opponent,我们上升两层回到 usersnewData.parent().parent()
  2. 然后我们向下进入对方的节点:child(newData.val())
  3. 然后我们验证对方的opponent属性是否匹配我们的uid:child('opponent').val() == $uid.

现在上面的两个写操作都会失败,因为它们一次只设置对手一个。要解决此问题,您需要执行 so-called multi-location update:

var updates = {};
updates['users/'+auth.uid+'/opponent'] = 'uid:1234';
updates['users/uid:1234/opponent'] = auth.uid;
ref.update(updates);

我们现在向 Firebase 服务器发送一条 update() 命令,将 uid 写入两个对手。这将满足安全规则。

一些注意事项:

  • 这些只是帮助您入门的一些示例。虽然它们应该有效,但您需要编写自己的规则来满足您的安全需求。
  • 这些规则只处理对手的写作。您可能还想测试游戏结束时会发生什么,您需要 清除 对手。

您还可以查看 transaction operation

Firebase 事务确保您正在处理的当前数据集确实是数据库中的数据,从而保证您正在更新处于正确状态的数据。文档表明这是避免您描述的竞争条件的推荐方法。

类似这样的东西(在 IOS 中,并且警告 - 未测试):

NSString* user1Key = @"-JRHTHaIs-jNPLXOQivY";
NSString* user2Key = @"-NFHUaIs-kNPLJDHuvY";

Firebase *user1Ref = [[Firebase alloc] initWithUrl: @"https://docs-examples.firebaseio.com.users/-JRHTHaIs-jNPLXOQivY/opponent"];
Firebase *user2Ref = [[Firebase alloc] initWithUrl: @"https://docs-examples.firebaseio.com.users/-NFHUaIs-kNPLJDHuvY/opponent"];

//See if the proposed opponent does not yet have a match
[user2Ref runTransactionBlock:^FTransactionResult *(FMutableData *opponent) {
if (opponent.value == [NSNull null]) {
    //They have no match - update with our key and signal success
    [opponent setValue:user1Key];
    return [FTransactionResult successWithValue: opponent];
} else {
    return [FTransactionResult abort];  //They already have an opponent - fail
    //Notify the user that the match didn't happen
   }    
} andCompletionBlock:^(NSError *error, BOOL committed, FDataSnapshot *snapshot) {
    if (!error && committed) {   
        //The transaction above was committed with no error 
        //Update our record with the other player - we're matched!
        [user1ref setValue:user2Key];

        //Do whatever notification you want
    } else {
       //Notify that the matchup failed 
    }

}];