FireBase - maintain/guarantee data consistency

北城以北 提交于 2019-12-06 14:50:26

问题


I'm trying to understand what is the right approach for this following scenario :

Multiplayer game,each game structured only with two players. Each game/match will be completely randomized

Lets assume 5 users "logs" the same time into my app, each one of them "searching" for a match. Each user hold a property named opponent which equal the the opponent uniqueID(initial value equal "". so far so good.

assuming user 1 matched with user 3. user 1 will update his own oppoent value to user 3 uniqueID and will do the same to user 3

Problem

1) What if at the same moment, user 2 tried to to the same to user 3? 2) What if at the same moment, user 3 tried to do so to user 4?

Main Point

Is it possible to "lock" a user values? or freeze them once they changed? Am i going in the wrong approach?

I was thinking using Security Rules and Validation in order to create consistency but i just may picked the wrong tech(FireBase). Any thoughts?

EDIT

Security rules i have tried, which still for some reason enable a third device change "already changed opponent" value.

{
    "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"
      }
      }

    }
}

回答1:


You can validate many things with Firebase security rules.

For example, you can say that an opponent can only be written if there currently is no opponent for the user:

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

With this and the following operations:

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

The second set() operation will fail, because the opponent property already has a value at that point.

You can expand that to also validate that the opponents must refer to each other:

"users": {
  "$uid": {
    "opponent: {
      ".write": "!data.exists()"
      ".validate": "newData.parent().parent().child(newData.val())
                    .child('opponent').val() == $uid"
    }
  }
}
  1. From the opponent that is being written, we go up two levels back to users: newData.parent().parent().
  2. Then we go down into the opponent's node: child(newData.val()).
  3. And we then validate that the opponent's opponent property matches our uid: child('opponent').val() == $uid.

Now both of the write operations from above will fail, because they're only setting the opponent one at a time. To fix this, you'll need to perform a so-called multi-location update:

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

We're now sending a single update() command to the Firebase server that writes the uids to both opponents. This will satisfy the security rule.

A few notes:

  • these are just some examples to get you started. While they should work, you'll need to write your own rules that meet your security needs.
  • these rules just handle writing of opponents. You'll probably also want to testing what happens when the game is over and you need to clear the opponents.



回答2:


You might also look at the transaction operation.

Firebase transactions make sure that the current set of data you are acting on is really what is in the database, guaranteeing that you are updating data that is in the right condition. The docs indicate that this is the recommended way to avoid race conditions such as you describe.

Something like this (in IOS, and warning - not tested):

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 
    }

}];


来源:https://stackoverflow.com/questions/34678083/firebase-maintain-guarantee-data-consistency

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!