事务概念
大部分情况下我们所说的事务都是数据库事务(Database Transaction),后来延时到了非关系型数据库等其他领域,事务是运行在我们数据库上的一个逻辑工作单元,运行在工作单元中的所有sql都具有原子性的操作特点。
数据库事务要满足ACID(更详细查看相关文章):
A:Atomic,原子性,事务必须是原子的工作单元,一个事务里面的所有操作要么全部成功,要么全部失败。
C:Consistency:一致性,事务完成时,保证所有数据的状态是一致性的。比如转账业务,有五个账户之间相互转账,无论转账多少次,怎么转账,五个账户的总余额都必须是不变的。
I:Isolation:隔离性,并发事务对同一资源所做的修改,事务之间所做的修改时隔离的,通过锁等手段实现。
D:Duration:持久性,事务完成之后,对系统的影响是永久性的,直到另一个事务执行改变其状态。
隔离性是通过锁实现的。
原子性、一致性、持久性是通过一些记录,比如事务日志、IO等实现。事务日志redo表示事务修改之后的状态记录,undo事务日志表示事务修改之前的状态记录,事务提交使用redo,事务回滚使用undo。
分布式事务
当你的系统是单机系统,并且使用单一数据库时,利用传统关系型数据库的事务特性来实现事务控制非常简单。但是,当你进行数据库分库分表、系统SOA话之后,传统关系型数据库的事务特性就不能满足事务需求了。
分布式事务指的是分布式环境下的事务。。。。
分布式事务产生的原因
- 数据库分库分表
当系统数据量达到一定规模时,就要对数据库进行分库分表,本来一个数据库就会分为多个数据库实例,拆分以后,一个操作可能就要对多个数据库实例进行事务操作。
应用系统有个操作需要对DB1修改一条数据,并且需要在DB2中新增一条数据,此时这两个操作要在同一个事务单元中,此时,就产生了一个分布式事务。
应用系统SOA化
原本的单体系统,单数据库,配合spring的声明式事务加上数据库的事务特性可以很容易实现事务控制,但是,当业务量达到一定程度后,需要对系统SOA话。比如原本的一个电商系统,拆分成如下几个子系统。
此时一个生成订单的操作,分为两步,订单表新增一条记录,库存表某条记录库存数减库存。这个操作应当在同一事务上进行,并且此时两个操作处于不同系统、不同数据库中,此时就产生了一个分布式事务。
2PC提交协议与3PC提交协议
2PC和3PC都是强一致性的协议。
2PC协议
2PC(Two Phase Commit),二阶段提交,是计算机网络,尤其是在数据库领域内,为了使得基于分布式系统架构下所有节点在进行事务处理过程中能够保持原子性和一致性设计的一种算法。目前,绝大部分关系型数据库都是采用二阶段提交协议来完成分布式事务的处理,利用该协议能够非常方便地完成所有分布式事务参与者的协调,同一决定事务的提交或者回滚。从而能够有效地保证分布式事务的一致性,因此二阶段提交协议被广泛应用到许多分布式系统中。
二阶段提交就是把事务的提交分为两个阶段来执行。
阶段一:提交事务请求
- 协调者像所有参与者发送事务内容,询问是否可以开始事务的操作,并等待个参与者的响应。
- 各参与者节点执行事务操作,并记录相应的事务日志Undo、Redo。
- 各参与者反馈给协调者事务的执行情况,执行成功返回yes、执行失败分会no。
阶段二:执行事务提交
协调者会根据参与者阶段一的反馈决定是提交事务还是回滚事务。
- 假设参与者全部都成功执行事务成功,返回yes,那么协调者就会像所有参与者发出Commit请求。
- 参与者接收到Commit请求后,会正式进行事务的提交,并再事务完成提交后释放事务占用的资源。
- 参与者在完成事务提交后,向参与者发送ack确认信息。
- 协调者接收到所有参与者反馈的ACK消息后,完成事务。
假设参与者有一个或者一个以上返回NO。
- 协调者向所有参与者发送回滚事务请求。
- 参与者接收到回滚事务请后,会利用在阶段1中记录的undo事务日志信息来执行事务的回滚操作,并在事务回滚完成后释放事务占用的资源。
- 参与者在事务回滚之后,像协调者返回ACK确认信息。
- 协调者接收到所有ACK之后,完成事务中断。
在两个阶段中,阶段一因为要涉及到记录事务日志,磁盘IO等耗时操作,所以2PC提交的时间消耗阶段一占据着绝大占比,而阶段二的操作相比起来就快很多,相当于一个非常短的时间操作。那么阶段二发生错误的概率会比阶段一小非常多,所以只要阶段一成功了,阶段二发生错误的概率是非常小的,因为阶段二是一个瞬时操作。那么就可以大大增加分布式事务的成功率。
优点:原理简单、实现方便。
缺点:同步阻塞、单点问题、脑裂、太过保守。
同步阻塞:
二阶段提交最明显也是最大的一个问题是同步阻塞问题,这会极大限制了分布式系统的性能,在二阶段事务的执行过程中,所有参与者的逻辑都处于阻塞状态,也就是说各个参与者在等待其他参与者响应的过程中,都处于阻塞状态,占用着资源。在阶段1,可能某些节点的事务执行得快,所以向协调接反馈得快,某些节点的事务执行得慢,所以向协调接反馈得慢,而协调者要接收到所有参与者的反馈才会执行阶段二操作,此时事务执行得快的参与者即使事务已经执行完了并作出了反馈,依然会阻塞等待阶段二的来临。
单点问题:
一旦协调者出现问题,那么整个二阶段事务提交将无法进行,更为严重的是如果协调者在阶段二中出现问题,比如来不及向参与者发事务提交请求就宕机了,那么所有参与者都会处于事务资源的锁定当中,而无法完成事务操作。这个也是属于同步阻塞问题。
数据不一致:
在阶段二时,协调者向参与者发送Commit提交请求时,发生局部网络异常(协调者与部分参与者无法通信)或者信息还没向全部参与者发送(只发送了部分)就突然宕机时,导致只有部分参与者收到了Commit请求,收到了Commit请求的参与者进行事务提交,而没有收到的参与者无法进行事务提交,于是整个分布式系统便出现了数据不一致性现象。
太过保守:
如果协调者指示参与者进行事务提交询问过程中,参与者出现故障而导致协调者始终无法获取到所以参与者的响应信息的话,这时,协调者只能依靠自身的超时机制判断是否需要中断事务。这样的策略显得比较保守,换句话说2阶段提交协议没有设计较为完善的容错机制。任意一节点的失败都会导致整个事务的失败。
3PC提交协议(Three Phase Commit)
基于2PC提交协议遇到的问题,出现了3PC提交协议。即三阶段提交,把二阶段提交的阶段一“提交事务请求”拆分成两个阶段,形成由CanCommit、PreCommit、doCommit三个阶段组成的分布式事务处理协议。
阶段一:Can Commit
- 事务询问:协调者向所有的参与者发送一个包含事务内容的canCommit请求,试问参与者是否可以执行事务的提交操作,并开始等待各参与者的响应。
- 各参与者像协调者反馈事务询问响应,参与者在接收到协调者的canCommit请求后,就判断自身是否可以进行该事务操作(判断的逻辑实现者自行确定,也许是仅仅判断参与者自身有没有宕机和与协调者的网络是否正常,也有可能还要根据事务资源状态来判断),经过判断后,如果认为自身可以执行事务的,就向协调者返回yes响应。否则返回No响应。
阶段二:Pre Commit
在阶段二,协调者会根据参与者在阶段一的反馈情况来决定是否可以进行事务的Pre Commit操作。包含两种可能:
- 在阶段一所有参与者都返回了Yes反馈,表示都可以执行事务操作。就进行事务预提交:
- 协调者向所有参与者发送事务与提交请求PreCommit,并进入Prepared阶段。
- 参与者接收到PreCommit请求后,会执行事务操作,并记录事务日志Redo、UnDo信息。
- 如果参与者成功执行了事务,就会反馈给协调者ACK响应,同时等待最终指令(提交/回滚)。
- 在阶段一中,假设有一个或一个以上的参与者返回了No信息,或者在超时时间内协调者没有接收到所有参与者的反馈。那么就会中断事务。
- 协调者向所有参与者发送中断请求(abort请求)。
- 无论是收到来自协调者的abort请求,还是在等待协调者消息时出现超时时,参与者都会执行中断事务操作。
阶段三:(Do Commit)
该阶段进行事务的真正提交。也会有两种情况出现。
- 在阶段三,假设协调者在超时时间内接收到了来自所有参与者的ACK反馈,就会进行事务提交。
- 协调者从预提交状态转换到提交状态,并向所有参与者发送doCommit请求。
- 参与者接受到doCommit请求后,会正式进行事务的提交操作,并再完成事务提交之后释放在整个事务期间占用的事务资源。
- 参与者在完成事务提交之后。向协调者发送ACK消息。
- 协调者接收到所有参与者的ACK消息之后,完成事务。
- 在阶段三,假设有一个或者一个以上的参与者没有返回给协调者ACK或者协调者在超时时间内没有收到来自所有参与者的ACK反馈,就进入事务中断状态。
- 发送中断请求,协调者向所有参与者发送abort请求。
- 参与者在接收到abort请求后,会利用阶段二记录的Undo事务日志进行回滚操作,并再回滚之后释放整个事务执行期间占用的资源。
- 参与者在完成事务回滚操作后,向协调者反馈ACK消息。
- 协调者接收到所有参与者反馈的ACK消息后,中断事务。
注意:
跟2PC有一个明显不同点是,2PC只有协调者有超时机制,参与者是没有超时机制的,也就是2PC时在阶段二参与者没有收到协调者的Commit请求会一直阻塞,占用事务资源。
3PC提交,参与者也会有超时机制,在阶段三时,假设协调者宕机或者协调者与参与者之间出现网络问题,最终导致参与者无法及时接收来自协调的在第三阶段的doCommit或者abort请求时,参与者在等待超时后,会进行事务提交操作。
优点:
3PC解决了2PC的前两个问题,即阻塞和单点问题。但是还是没能解决数据一致性问题。但是进一步减小了数据不一致的概率。
解决阻塞问题是减少了参与者的阻塞范围,这个优化点,主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
解决了单点问题:因为参与者在超时后会自动进行本地commit从而进行释放资源。所以当协调者宕机或者与参与者之间出现网络故障时,并且提交事务并释放资源,假设所有参与者都没有接收到协调者的阶段三的请求时,或者只接收了doCommit请求时,事务也能保持一致,因为接收到的参与者会执行提交操作,超时的参与者也会执行提交操作。
但是3PC也会出现数据一致性问题:
在阶段三:假设阶段二时,有一个或者一个以上的参与者反馈给协调者ACK反馈,或者协调者在超时时间内没有收到所有参与者的ACK反馈,协调者在阶段三就会向参与者发送abort请求,但是由于协调者与部分参与者存在网络故障或者协调者在发送给了部分参与者abort请求后就宕机了,导致部分参与者由于超时而提交事务,部分参与者由于接收到了abort请求而回滚事务,这就导致了不一致问题。
减小了数据不一致的概率:
在2PC提交协议,在第二阶段无论协调者发送Commit请求还是abort请求给参与者,只要出现由于协调者与部分参与者存在网络故障或者协调者在发送给了部分参与者abort请求后就宕机了的情况,就会导致数据不一样。
在3PC提交协议,在第三阶段,如果出现由于协调者与部分参与者存在网络故障或者协调者在发送给了部分参与者abort请求后就宕机了的情况的这个情况,当能接收到协调者消息的部分参与者接收到的消息是doCommit消息,数据还是能够保持一致的,因为超时的参与者也是执行提交操作,只有当协调者发送给部分参与者的消息是abort请求时,才会出现数据不一致。
以上两个协议都不能完全解决数据一致性问题,还得通过一些补偿机制来实现事务一致性。
分布式事务的解决方案
X/OpenDTP事务模型:
X/OpenDTP(X/Open Distributed Transaction Procession Reference Model):是X/Open这个组织定义的一套分布式事务的标准,也就是定义了规范的API接口,由各个厂商进行具体实现。
这个标准提出了使用二阶段提交来保证分布式事务的完成性,后来J2EE也遵循了这套规范,设计并实现了java里的分布式事务编程接口规范-JTA。
X/A事务是 X/Open DTP定义的中间件与数据库之间的接口规范。X/A接口函数由数据库厂商提供。
X/OpenDTP事务模型的角色:
X/OpenDTP事务模型有三个角色:
AP:Application,也就是我们的应用系统。
RM:Resources Manager,资源管理器,也就是我们的数据库。
TM:Transaction Manager,事务管理器、事务协调者。负责分布式事务的协调。
流程就与2PC提交一样。AP类比参与者,TM类比协调者。
Java X/OpenDTP事务模型的实现:
- JOTM(Java Open Transaction Manager):基于javaee JTA规范实现的。
- Atomikos ,原本是一个商业项目,后来开源了。
这篇就不展开讲这两个了。