首先,让我们讨论一下为什么有人可能想要在多个数据路径上进行提交/回滚......
你需要这个吗?
一般来说,如果出现以下情况,则不需要此选项:
- 您没有以高并发性进行写入(不同用户每分钟对同一记录进行数百次写入操作)
- 您的依赖关系很简单(B 依赖于 A,C 依赖于 A,但 A 不依赖于 B 或 C)
- 您的数据可以合并到单个路径中
开发人员有点过于担心数据中出现孤立记录。
Web 套接字在一次写入和另一次写入之间失败的可能性可能微不足道,并且与之间的冲突顺序类似。
基于时间戳的 ID。这并不是说这是不可能的,但它通常后果很低,可能性很小,不应该是您的主要关注点。
此外,使用脚本甚至只需在 JS 控制台中输入几行代码即可非常轻松地清理孤儿。再说一次,
它们的后果往往非常低。
除了这个你还能做什么呢?
将必须以原子方式写入的所有数据放入单个路径中。然后你可以把它写成一个单一的set or a 交易如果需要的话。
或者,如果一条记录是主记录,而其他记录依赖于此,则只需先写入主记录,然后在回调中写入其他记录即可。添加安全规则来强制执行此操作,以便主记录始终存在,然后才允许其他记录写入。
如果您只是为了轻松快速地迭代数据而对数据进行非规范化(例如,获取用户名称列表),那么只需在单独的路径中索引该数据即可。
然后,您可以将完整的数据记录放在单个路径中,并将姓名、电子邮件等放在快速、查询/排序友好的列表中。
这什么时候有用?
如果您有一组非规范化记录,则这是一个合适的工具:
- 无法以实际的方式合并到一条路径中
- 具有复杂的依赖关系(A 依赖于 C,C 依赖于 B,B 依赖于 A)
- 记录以高并发性写入(即不同用户每分钟可能对同一记录进行数百次写入操作)
你怎么做到这一点?
这个想法是使用更新计数器来确保所有路径保持相同的修订版本。
1) 创建一个更新计数器,该计数器使用事务递增:
function updateCounter(counterRef, next) {
counterRef.transaction(function(current_value) {
return (current_value||0)+1;
}, function(err, committed, ss) {
if( err ) console.error(err)
else if( committed ) next(ss.val());
}, false);
}
2)给它一些安全规则
"counters": {
"$counter": {
".read": true,
".write": "newData.isNumber() && ( (!data.exists() && newData.val() === 1) || newData.val() === data.val() + 1 )"
}
},
3) 为您的记录提供安全规则以强制执行 update_counter
"$atomic_path": {
".read": true,
// .validate allows these records to be deleted, use .write to prevent deletions
".validate": "newData.hasChildren(['update_counter', 'update_key']) && root.child('counters/'+newData.child('update_key').val()).val() === newData.child('update_counter').val()",
"update_counter": {
".validate": "newData.isNumber()"
},
"update_key": {
".validate": "newData.isString()"
}
}
4)用update_counter写入数据
由于您已制定安全规则,因此只有在计数器不移动的情况下才能成功写入记录。如果它确实移动,那么记录已被并发更改覆盖,因此它们不再重要(它们不再是最新和最好的)。
var fb = new Firebase(URL);
updateCounter(function(newCounter) {
var data = { foo: 'bar', update_counter: newCounter, update_key: 'myKey' };
fb.child('pathA').set(data);
fb.child('pathB').set(/* some other data */);
// depending on your use case, you may want transactions here
// to check data state before write, but they aren't strictly necessary
});
5)回滚
回滚涉及更多一些,但可以根据以下原则构建:
- 在调用 set 之前存储旧值
- 监控每个设置操作是否失败
- 对任何已提交的更改设置回旧值,但保留新计数器
预建库
我今天写了一个库来执行此操作塞到了 GitHub 上。请随意使用它,但请确保阅读“您需要这个吗?”不会让您的生活变得复杂。多于。