解决方案4: Log Reconciliation 很多财务系统常用的解决方案是 log reconciliation。这种解决方案将事务写作简单的日志,这避免了复杂性和潜在的故障。然后从上次良好状态以来所有的变化推测当前账户的状态。在极端情况下,你可以清空账户,然后通过实施从第一天以来所有的变化重建账户……这听起来很恐怖,但是可行。账户文件需要一个“缓存”来提高速度,还需要一个seqId,seqId计算如下: 执行事务时,一个典型的财务系统会给事务写一个条目,会给与事务有关的账户写一个“账户变化”条目。这个方法需要进一步的写保证,“作业队列”解决方案可以实现写保证,事务中所有的作业在所有账户更改写入前都会保持不变。不过有了MongoDB,我们可以写一个包括事务和账户更改的文档。这个文档应该嵌入tx集合,如下: 几个重点:
关键是确保即使事务没有按顺序发生,缓存平衡也可以安全的计算/取消,还有就是事务状态可能改变。因此我们每个账户使用一个seqId,这确保了账户更改按确定的顺序发生,可以避免复杂的锁。在写事务前,应用首先通过简单地查询推断每个账户的下一个sqlId: 然后每个sqlId都本地增长,然后写作事务的一部分。如果另一个线程也可能同时包括同样的seqId,独特的索引会确保写失败,线程会进行重试直到顺利完成任务。另一种方法是在账户集中保存一个当前seqId,然后用 findAndModify()获得下一个seqId,这通常会比较慢,除非你对账户有很多争用。注意如果因为某种原因事务没有写时,seqId可能会被跳过去,不过只有没有副本情况下才会成为。 下面我们谈谈reconciliation的基础。后台进程确保所有未提交的事务都会继续进行。只有所有账户的低seqId的事务都提交后一个事务才会被标注为提交。事务被标记为提交后就会变成不可变的。下面来谈谈好的方面:获得账户平衡。首先我们获得好的平衡,我们可以通过索引进行查询: 我们通过较大seqId的事务获得所有将发生的更改: 我们可以使用这些解决展示即将发生的损耗。如果我们只想简单的了解将来的平衡点在哪,我们可以让MongoDB收集所有变更展示总数: 为了确保系统快速、计算量小,后台工作者要确保所有的事务都达到提交状态,平衡得到缓存。理想情况下一个事务是不可逆的,取而代之的是提交一个逆向事务来实施事务。不过只要所有的进一步事务状态和缓存都是正确设置的,取消是可行的。 解决方案5:版本控制 有时变得很复杂,以至于不能再JSON中表示,这些变更可能涉及很多有着复杂关系的文件(如树结构)。如果仅是部分变化(如破坏树)将会很混乱,这种情况下我们需要隔离。获取隔离性的一种方式是插入有着高版本号的新文档,取代对现有文档的更新。可以通过同日志和解同样的技术很容易、很安全的获得新版本号。通常{ itemId: 1, version: 1}上有一个独特的索引。 嵌入文档的应用从子文档开始,到主文档结束(如根节点)。当获取数据时,应用检查主文档的版本号,忽略高于版本号高于此版本号的文档。未完成的事务可以保持原状,可以忽略,可以清楚。 总结 综上所述,我们提供了在文档间实施鲁棒可扩展事物的五种解决方案:
此外,我们还提到了很多次MongoDB最终将支持真正的原子性和文档间的隔离事务。这已经作为分区的一部分了,但目前还只是内部的……只有文档在同一分区时这一特性才可能实现,否则我们将回到不可扩展的SQL世界。 原文链接: How to Implement Robust and Scalable Transactions Across Documents with MongoDB(编译/蔡仁君 责编/仲浩) |