FireBase – 维护/保证数据的一致性
我试图了解什么是这种情况下正确的方法:
多人游戏,每个游戏only
两个玩家结构。 每场比赛将完全随机化
让我们假设5个用户同时“login”到我的应用程序中,每个用户都在search一个匹配项。 每个用户都拥有一个名为“ opponent
的属性,它等于对手的uniqueID
(初始值等于""
。
假定用户1与用户3匹配。用户1将他自己的oppoent
值更新为用户3的oppoent
, 并且将对用户3 执行相同的操作
问题
1)如果同时用户2试图对用户3做同样的事情呢? 2)如果用户3同时尝试向用户4这么做?
要点
是否有可能“locking”用户值? 或冻结他们,一旦他们改变了? 我会采取错误的做法吗?
我想使用Security Rules
和Validation
,以创build一致性,但我可能会select错误的技术(FireBase)。 有什么想法吗?
编辑
我已经尝试过的安全规则,这仍然是由于某种原因,使第三个设备更改“已经改变的对手”的价值。
{ "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安全规则validation许多事情。
例如,你可以说只有在对手没有对手的情况下才能写对手:
"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
属性在那个点上已经有了一个值。
你可以扩展它来validation对手必须相互引用:
"users": { "$uid": { "opponent: { ".write": "!data.exists()" ".validate": "newData.parent().parent().child(newData.val()) .child('opponent').val() == $uid" } } }
- 从正在写入的
opponent
,我们回到users
两个级别:newData.parent().parent()
。 - 然后我们进入对手的节点:
child(newData.val())
。 - 然后我们validation对手的
opponent
属性是否与我们的uid:child('opponent').val() == $uid
匹配。
现在上面的两个写操作都会失败,因为他们只是一次一个地设置对手。 要解决这个问题,你需要执行一个所谓的多位置更新 :
var updates = {}; updates['users/'+auth.uid+'/opponent'] = 'uid:1234'; updates['users/uid:1234/opponent'] = auth.uid; ref.update(updates);
我们现在将一个update()
命令发送给Firebase服务器,将uid写入两个对手。 这将满足安全规则。
一些注意事项:
- 这些只是让你开始的一些例子。 虽然他们应该工作,你需要编写自己的规则,以满足您的安全需求。
- 这些规则只是处理对手的写作。 您可能还想testing游戏结束时发生的情况,并且需要清除对手。
您也可以查看事务操作 。
Firebase事务处理确保您正在处理的当前数据集实际上是数据库中的数据,确保您正在更新处于正确状态的数据。 该文件表明,这是避免竞争条件,如你所描述的build议的方式。
这样的东西(在IOS中,并警告 – 未经testing):
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 } }];