注:本文章引自终于把分布式事务讲明白了!
分布式事务
分布式事务是指在分布式环境下事务,一个事务由多个数据库节点共同完成。分布式事务也必须要保证事务的ACID的特性。
实现分布式事务原子性的通常做法就是采用两阶段提交协议,不过也有特殊的使用一阶段提交+补偿回滚机制。
两阶段提交协议
一阶段提交是无法保证分布式事务的一致性的,举个简单的转账的例子,B向C转账100元,B和C在两个不同的节点上。由于分布式环境中无法保证两个节点的时钟完全一致,这样有可能会发生B扣款成功但C收款失败的情况,因此就无法保证这个事务是一致的。
为了解决分布式环境下的事务一致性问题,Jim Gray等研究者在1978年提出了两阶段提交协议。两阶段提交协议可以用于单机系统中由事务管理器协调多个资源管理器,也可以用于分布式系统中由一个全局的事务管理器协调各个子系统的局部事务管理器完成两阶段提交。
经典的两阶段提交协议中,有两个角色,即协调者(Coordinator)和参与者(Participant)。
事务的提交分成2个阶段,第一阶段称为投票(prepare)阶段,第二阶段称为决定(commit)阶段。在第一阶段,假设有一个事务完成操作并决定提交,首先需要由协调者发一个prepare消息给参与者,参与者们接收到消息后,根据自己的实际情况,如果可以处理提交就回复一个ready告知已经准备好提交。当协调者接收到所有参与者的确认消息后才会发送commit消息让参与者们进行提交。如果有节点回复无法提交则整个事务将不能执行,协调者会发送abort命令给所有参与者。
下面结合两阶段协调和日志操作来看一下具体是怎样实现的,我们假设协调者是A,参与者是B和C。
- 第1阶段。协调者A的事务管理器将 <prepare,T> 加入日志中并强制写到磁盘,然后将prepare T的消息发到所有参与者B和C。B和C的事务管理器在接到prepare消息后,决定是否提交T中属于自己的那部分操作,如果不能提交,事务管理器就在日志中加一个<not prepared,T>记录,并且向A发送abort T作为响应,如果能提交,事务管理器就在日志中加一个<ready,T>记录并且将所有与T相关的日志强制写到磁盘,然后向A回复一条ready T作为响应。
- 第2阶段。当A收到所有参与者消息回答后,就可以确定T是提交还是终止。如果A收到所有参与者的ready应答消息后,该事务可以提交,否则该事务就必须回滚。提交事务T时,将<commit,T>(或<abort,T>,如果中止事务)日志记录写入日志,并且强制写到磁盘,并且将commit消息(或abort消息)发给所有参与者,其他节点收到commit消息后将<commit,T>写入日志。最后,参与者向协调者发送确认消息,A收到所有参与者确认消息后,把记录<complete,T>加入日志文件中。
在两阶段提交过程中,可能会发生一些故障,比如:
- 参与者故障。如果参与者故障后恢复,根据日志记录来决定重做或撤销事务T。是否有<ready,T>记录或<commit,T>记录。
- 协调者故障。如果协调者发生故障,在某些情况下,参与者会不知道是否提交事务,所以必须等协调者从失败中恢复。
如果协调者回复的时间不确定,参与者需要持有事务T的资源,等待协调者消息,这会造成阻塞。如果由于网络原因,协调者的提交或撤销消息迟迟不能发到参与者,也会造成阻塞。针对这种阻塞问题,业界提出的优化比如三阶段提交,就是在协调者和参与者上面增加了一些超时的机制,不过三阶段的实现代价较高,目前很少在业界使用。两阶段协议虽然存在一些问题,但数据库实现者可以通过一些方式来规避。