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 Rules
和 Validation
来创建一致性,但我可能选择了错误的技术 (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"
}
}
}
- 从正在写入的
opponent
,我们上升两层回到 users
:newData.parent().parent()
。
- 然后我们向下进入对方的节点:
child(newData.val())
。
- 然后我们验证对方的
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
}
}];
我正在尝试了解以下情况的正确方法:
多人游戏,每局 only
两名玩家。每个game/match将完全随机化
让我们假设 5 个用户 "logs" 同时进入我的应用程序,每个用户 "searching" 匹配一次。每个用户持有一个名为 opponent
的 属性 等于对手 uniqueID
(初始值等于 ""
。到目前为止一切顺利。
假设用户 1 与用户 3 匹配。用户 1 会将自己的 oppoent
值更新给用户 3 uniqueID
并将对用户 3 执行相同的操作
问题
1) 如果在同一时刻,用户 2 尝试对用户 3 进行同样的操作怎么办? 2) 如果在同一时刻,用户 3 试图对用户 4 这样做怎么办?
要点
是否可以 "lock" 用户值?或者一旦他们改变就冻结他们?我走错路了吗?
我正在考虑使用 Security Rules
和 Validation
来创建一致性,但我可能选择了错误的技术 (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"
}
}
}
- 从正在写入的
opponent
,我们上升两层回到users
:newData.parent().parent()
。 - 然后我们向下进入对方的节点:
child(newData.val())
。 - 然后我们验证对方的
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
}
}];