MongoDB Two-phase Commit

先恶补一下分布式事务中事务的各类提交机制:(关于分布式事务、两阶段提交、一阶段提交、Best Efforts 1PC模式和事务补偿机制的研究)
NoSQL并没有关系数据库那么严格的事务机制,所以在NoSQL中能否很好的实现事务功能体现了开发人员的功力。两段式提交保证了分布式XA规范的分布式事务的原子性。两段指prepare阶段和commit阶段:


Prepare:TM(transaction manager)给每个参与者(resource manager)发送prepare信息。每个参与者要么直接返回失败,要么在本地执行事务(记录日志和rollback的信息),但不commit。
Commit:如果TM收到了任一参与者的失败消息或超时,那么TM会发rollback给其他的参与者,参与者会执行rollback并在最后释放锁资源。否则则发送commit让所有参与者完成事务。
这样能保证在事务提交前尽可能完成所有能完成的工作,最后的commit是一个耗时很短的操作,错误概率相对很低。相比单一阶段的commit,两段式更加可靠但是会消耗更多时间,所以会提高锁资源的冲突,加大了死锁的发生几率。

1. MongoDB的事务

在MongoDB中对于单一文档的操作当然是原子操作,所以在单一文档中加入多个嵌入的设计也能做到这种原子性。但是对于多文档的操作,mongodb往往做不到“全做”或者“全不做”的事务操作。当执行包含多个顺序操作的事务时,下面的问题就会出现:
原子:如果一个操作失败,同一事务中先前的操作必须要回滚(“全不做”)。
隔离:事务操作可以并行运行,必须在该事务处理过程中能看到一个一致性的数据视图。
一致:如果出现了严重故障(网络、主机)中断了一个事务,数据库必须有能力回滚这个一致性状态。
MongoDB中可以使用两段式方法来实现这个事务功能。

2. MongoDB中的两段式提交

事务中最典型的example是通过可靠的方式将A账户中的资金转移到B账户。在关系数据库中,这种可以使用原子事务的形式轻松得到实现,而在MongoDB中,你可以使用两段式提交来得到一个类似的实现。
MongoDB中的accounts集合保存了账户的name和余额balance信息,以及当前事务的数组pendingTransactions
。首先创建两个账户:

然后需要创建一个“事务”集合transactions,来保存事务的信息——源账户source、目标账户dest、转移金额value、事务状态state。如果通过账户A转移一笔钱到账户B时,将通过下面的方式发起一个事务(state为initial)

在修改accounts集合的事务状态之前,要把事务集合中的状态设置为pending:
a). 使用一个局部变量t找到处于一个initial状态的事务

b). 然后更新t的状态为pending:

c). 为每一个账户应用事务,并记录事务的_id。有点类似于Oracle中的ITL。

检查一下accounts集合的状态:

到此为止,二段式提交中prepare阶段基本已经完成。而大部分错误都可能在之前的步骤中出现,如果这其中有失败的错误,而根据transaction的信息来回滚。

d). 将事务状态设置为commited

然后更新accounts集合,相当于一个释放锁的过程。

e). 将事务状态设置为done,正式完成这一个事务。

3. 恢复错误的事务操作

关于出现失败后的recover,主要有下面两种情况:
(1). failure出现在b(设置状态为pending)和d(设置状态为committed)之间,一般是应用事务时出现错误。如果要恢复这个事务,应用需要获得pending状态的事务列表,并重做应用事务的操作。
(2). failure出现在d(设置状态为committed)和e(设置状态为done)之间。如果要恢复这个事务,应用需要获得commited状态的列表,并在accounts集合中重做移除pending事务的操作。

4. 回滚

当出现一些不可恢复的错误时(例如其中一个文档不存在、文档条件(balance)不满足等),就需要进行回滚操作。有两种可能的回滚方法:
(1). 完成f.设置事务状态为done后,你需要完全提交这个事务,而不能进行回滚操作。可以创建一个新的事务来转换源和目标的文档。
(2). 完成e.设置事务状态为pending和d.将事务状态设置为commited之间,你需要执行下面的步骤:
a). 设置事务状态为canceling

b). 回滚事务:执行一系列的相反的操作。

c). 设置事务状态为canceled.

到此,回滚操作就已经完成。

^^

Posted in Database, JavaScript, NoSQL.