ServerValue.increment 网络中断时无法正常工作
ServerValue.increment doesn't work properly when Internet goes down
添加 ServerValue.increment()
(Add increment() for atomic field value increments #2437) 是一个好消息,因为它允许在 Firebase RTDB 中自动增加字段值。
我有一个保留库存的应用程序,这个功能一直很关键,因为它允许更新库存,而不管用户有时是否离线。但是,我开始注意到有时该函数会执行两次,这完全以错误的方式错误地陈述了库存。
为了隔离问题,我决定进行以下测试,结果表明当连接从在线转到离线时 ServerValue.Increment()
工作不正常:
从 1 到 200 做一个 for loop function
:
for (var i = 1; i <= 200; i++) {
testBloc.incrementTest(i);
print('Pos: $i');
}
函数 incrementTest(i)
必须递增两个变量:position
(从 1 到 200)和 sum
(加 1 + 2 + 3 , ..., + 200 结果应该是 20,100)
Future<bool> incrementTest(int value) async {
try {
db.child('test/position')
.set(ServerValue.increment(1));
db.child('test/sum')
.set(ServerValue.increment(value));
} catch (e) {
print(e);
}
return true;
}
请注意,db 指的是 Firebase 实例 (FirebaseDatabase.instance.reference()
)
有了这个,测试就来了:
测试 1:100% 在线。通过
该函数正常工作,使两个变量得到正确的结果(在 Firebase 控制台中):
position: 200
sum: 20100
测试 2:100% 离线。通过
为此,我在飞行模式下使用了物理设备,然后我执行了 for loop function
,当函数执行完毕后,我停用了飞行模式并在 firebase 控制台中检查了结果,结果令人满意:
position: 200
sum: 20100
测试 3:开始在线,然后转到离线。失败
这是网络连接中断时的典型操作场景。更糟糕的是,当连接断断续续时,您正乘坐地铁旅行或处于低覆盖率站点,而离线持久性是理想的功能。为了模拟它,我所做的是在线模式下 运行 for loop function
,在完成之前,我将物理设备置于飞行模式。后来我去Online完成了测试,在Firebase控制台看到了结果。在所有情况下获得的结果都是不正确的。以下是部分结果:
如您所见,Increment 被错误地重复了 10、18 和 9 次。
如何避免这种行为?
是否有任何其他方法可以在 Firebase 中自动递增一个在线/离线正常工作的数字?
firebaser 在这里
这是增量行为中一个有趣的边缘案例。客户端和服务器之间都无法确定是否执行了增量,因此最终会在重新连接时从客户端重试。据我所知,这个问题只会发生在增量操作上,因为所有其他写操作都是幂等的,除了事务,但那些在离线时不起作用。
可以确保每个增量只发生一次,但这需要一些工作:
- 首先,为写入操作添加一个随机数,唯一标识此操作。您可以为此使用按键,但任何其他 UUID 也可以正常工作。将其与您最初的
set()
调用合并为一个多路径 update
调用,将随机数写入顶级节点,并将服务器端时间戳作为其值。
- 现在在顶级位置的安全规则中,只有在没有现有数据的情况下才允许写入。这确保了您看到的次级写入被拒绝,并且由于安全规则是在整个多路径更新中检查的,因此错误的增量也会被拒绝。
- 您可能希望根据其中的时间戳值使用随机数键定期清理节点。这对性能无关紧要(因为在清理期间您永远不会在此处搜索),但可能有助于控制随机数的存储成本。
我还没有将这种方法用于这个特定的用例,但已经为其他人使用过。如果您包含客户端重试,则上述内容实质上构建了您自己的多路径事务机制,这正是我过去所需要的。但是因为你在这里不需要它,所以没有它更简单。
根据@puf的回答,您可以进行如下处理:
Future<bool> incrementTest(int value, int dateOfToday) async {
var id = db.push().key;
Map<String, dynamic> _updates = {
'test/position': ServerValue.increment(1),
'test/sum': ServerValue.increment(value),
'test/nonce/$id': dateOfToday,
};
db.child('previousPath').update(_updates)
.catchError((error) => print('Increment Duplication Rejected ${error.message}'));
return true;
}
然后,在 Firebase 安全规则中,您需要在 test/nonce/id
位置添加一条规则。内容如下:
{
"previousPath": {
"test": {
".read": "auth != null", //It depends on your root rules
".write": "auth != null", //It depends on your root rules
"nonce": {
"$nonce_id": {
".validate": "!data.exists()" //THE MAGIC IS HERE
}
}
}
}
}
这样,当设备再次尝试(错误地)写入数据库时,Firebase 将拒绝它,因为它之前已经用相同的 ID 进行了写入。
我希望它对其他人有用!!!
添加 ServerValue.increment()
(Add increment() for atomic field value increments #2437) 是一个好消息,因为它允许在 Firebase RTDB 中自动增加字段值。
我有一个保留库存的应用程序,这个功能一直很关键,因为它允许更新库存,而不管用户有时是否离线。但是,我开始注意到有时该函数会执行两次,这完全以错误的方式错误地陈述了库存。
为了隔离问题,我决定进行以下测试,结果表明当连接从在线转到离线时 ServerValue.Increment()
工作不正常:
从 1 到 200 做一个
for loop function
:for (var i = 1; i <= 200; i++) { testBloc.incrementTest(i); print('Pos: $i'); }
函数
incrementTest(i)
必须递增两个变量:position
(从 1 到 200)和sum
(加 1 + 2 + 3 , ..., + 200 结果应该是 20,100)Future<bool> incrementTest(int value) async { try { db.child('test/position') .set(ServerValue.increment(1)); db.child('test/sum') .set(ServerValue.increment(value)); } catch (e) { print(e); } return true; }
请注意,db 指的是 Firebase 实例 (FirebaseDatabase.instance.reference()
)
有了这个,测试就来了:
测试 1:100% 在线。通过
该函数正常工作,使两个变量得到正确的结果(在 Firebase 控制台中):
position: 200
sum: 20100
测试 2:100% 离线。通过
为此,我在飞行模式下使用了物理设备,然后我执行了 for loop function
,当函数执行完毕后,我停用了飞行模式并在 firebase 控制台中检查了结果,结果令人满意:
position: 200
sum: 20100
测试 3:开始在线,然后转到离线。失败
这是网络连接中断时的典型操作场景。更糟糕的是,当连接断断续续时,您正乘坐地铁旅行或处于低覆盖率站点,而离线持久性是理想的功能。为了模拟它,我所做的是在线模式下 运行 for loop function
,在完成之前,我将物理设备置于飞行模式。后来我去Online完成了测试,在Firebase控制台看到了结果。在所有情况下获得的结果都是不正确的。以下是部分结果:
如您所见,Increment 被错误地重复了 10、18 和 9 次。
如何避免这种行为?
是否有任何其他方法可以在 Firebase 中自动递增一个在线/离线正常工作的数字?
firebaser 在这里
这是增量行为中一个有趣的边缘案例。客户端和服务器之间都无法确定是否执行了增量,因此最终会在重新连接时从客户端重试。据我所知,这个问题只会发生在增量操作上,因为所有其他写操作都是幂等的,除了事务,但那些在离线时不起作用。
可以确保每个增量只发生一次,但这需要一些工作:
- 首先,为写入操作添加一个随机数,唯一标识此操作。您可以为此使用按键,但任何其他 UUID 也可以正常工作。将其与您最初的
set()
调用合并为一个多路径update
调用,将随机数写入顶级节点,并将服务器端时间戳作为其值。 - 现在在顶级位置的安全规则中,只有在没有现有数据的情况下才允许写入。这确保了您看到的次级写入被拒绝,并且由于安全规则是在整个多路径更新中检查的,因此错误的增量也会被拒绝。
- 您可能希望根据其中的时间戳值使用随机数键定期清理节点。这对性能无关紧要(因为在清理期间您永远不会在此处搜索),但可能有助于控制随机数的存储成本。
我还没有将这种方法用于这个特定的用例,但已经为其他人使用过。如果您包含客户端重试,则上述内容实质上构建了您自己的多路径事务机制,这正是我过去所需要的。但是因为你在这里不需要它,所以没有它更简单。
根据@puf的回答,您可以进行如下处理:
Future<bool> incrementTest(int value, int dateOfToday) async {
var id = db.push().key;
Map<String, dynamic> _updates = {
'test/position': ServerValue.increment(1),
'test/sum': ServerValue.increment(value),
'test/nonce/$id': dateOfToday,
};
db.child('previousPath').update(_updates)
.catchError((error) => print('Increment Duplication Rejected ${error.message}'));
return true;
}
然后,在 Firebase 安全规则中,您需要在 test/nonce/id
位置添加一条规则。内容如下:
{
"previousPath": {
"test": {
".read": "auth != null", //It depends on your root rules
".write": "auth != null", //It depends on your root rules
"nonce": {
"$nonce_id": {
".validate": "!data.exists()" //THE MAGIC IS HERE
}
}
}
}
}
这样,当设备再次尝试(错误地)写入数据库时,Firebase 将拒绝它,因为它之前已经用相同的 ID 进行了写入。
我希望它对其他人有用!!!