分布式强一致性事务
一、事务的概念
事务是一组操作的执行单元,相对于数据库操作来讲,事务管理的是一组SQL指令,比如增加,修改,删除等,事务的一致性,要求,这个事务内的操作必须全部执行成功,如果在此过程种出现了差错,比如有一条SQL语句没有执行成功,那么这一组操作都将全部回滚
最经典的例子便是:A向B汇款500元,B账户多了500元,这整个过程,要么全部正常执行,要么全部回滚,不然就会出现A扣款,B收不到钱,或者A没扣款,B收到500元的情况,这种场景是灾难性的。
事务特性(ACID)
(1)Atomic(原子性):要么都成功,要么都失败
(2)Consistent(一致性):数据应该不被破坏
(3)Isolate(隔离性):用户间操作不相混淆
(4)Durable(持久性):永久保存
二、本地事务和分布式事务
本地事务:本地事务是在同一个JVM实例中调用不同的资源形成事务,紧密依赖于底层资源管理器(例如数据库连接 ),事务处理局限在当前事务资源内。此种事务处理方式不存在对其他应用服务器或实例的依赖。
如上述说到的汇款案例来说,若两个过程都在同一个JVM实例中,使用同个数据库连接,则事务比较好控制,如下图:
分布式事务:分布式事务是在一个JVM实例中调用不同的服务形成事务,不同的服务可能不在同一个JVM实例中,可能涉及到操作多个数据库的事务,必须保证每个独立节点上的数据操作可以满足ACID。
如上述说到的汇款案例来说,若两个过程在不同的JVM实例中,使用不同的数据库连接,则事务不好控制,原因是A,B无法知道事务在其他地方所做的所有动作,无法实现事务的一致性,如下图,A,B各自使用自己的事务管理完成begin,commit,rollback等操作,AB整个过程是无法实现事务一致性的。
三、对分布式事务的处理
在分布式系统中,各个节点之间在物理上相互独立,通过网络进行沟通和协调。但是相互独立的节点无法知道其他节点的事务执行情况,要保证所有节点上的写操作,要么全部提交,要么全部rollback,最好的办法就是引入coordinator,即协调者来协调和通知各个节点上的事务执行情况。
Java 事务编程接口(JTA:Java Transaction API)和 Java 事务服务 (JTS:Java Transaction Service) 为 J2EE 平台提供了分布式事务服务。分布式事务(Distributed Transaction)包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager )。我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器承担着所有事务参与单元的协调与控制。
由于事务管理器统一管理所有事务参与单元,则所有事务参与单元都应实现同一套协议,才方便事务管理器的管理,就好似一个老师A管理一班学生,所有学生按照老师约定好的形式完成作业,之后交由同一个老师批改,批改后统一退回。若同一个作业,部分学生按照A老师的标准完成,部分同学按照B老师的标准完成,再提交上去批改,则最终得到的结果就可能互相不一致了。
四、XA协议
分布式事务中,所有的事务单元都需要实现统一的协议,XA 就是定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。 XA 接口函数由数据库厂商,或消息中间件厂商提供。
如下图,我们可以将 JTA 的事务管理器和资源管理器理解为两个方面:面向开发人员的使用接口(事务管理器)和面向服务提供商的实现接口(资源管理器),开发人员使用开发人员接口,实现应用程序对全局事务的支持;各提供商(数据库,消息中间件等)依据提供商接口(即XA接口)的规范提供事务资源管理功能;事务管理器( Transaction Manager )将应用对分布式事务的使用映射到实际的事务资源并在事务资源间进行协调与控制,使得 JTA 可以在异构事务资源之间执行协同沟通。
五、强一致性事务实现原理
2PC,即二阶段提交,是分布式事务中一个很重要的协议,当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个coordinator,即协调者作为的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交或回滚。
二阶段提交的算法思路可以概括为: 参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是回滚操作。
各参与者成功提交事务流程:
(1)在一个有A,B,C参与的事务中,引入一个协调者,用来协调和通知各参与者执行相应的事务动作,其中黑线为协调者发起的指令,红线为参与者响应的指令。
(2)协调者通知所有参与者节点准备提交事务,参与者节点执行事务操作,并将Undo信息和Redo信息写入日志,但此时不提交事务。
(3)协调者收到各参与者准备提交完成的指令,没有任何节点出错,此时通知所有节点提交事务。
(4)参与者节点正式完成提交操作,并释放在整个事务期间内占用的资源。
(5)协调者收到所有参与者确认提交完毕的答复,此时整个事务完成。
各参与者提交事务失败流程:
(1)在一个有A,B,C参与的事务中,引入一个协调者,用来协调和通知各参与者执行相应的事务动作,其中黑线为协调者发起的指令,红线为参与者响应的指令。
(2)协调者通知所有参与者节点准备提交事务,参与者节点执行事务操作,并将Undo信息和Redo信息写入日志,但此时不提交事务,但此时B节点在准备执行操作事务时出现了异常,则返回给协调者准备失败的响应信息
(3)协调者虽然收到A跟C准备完成的响应,但B返回的状态是准备失败,此时进入通知所有参与者回滚的阶段
(4)参与者节点正式完成回滚操作,并释放在整个事务期间内占用的资源。
(5)协调者收到所有参与者确认回滚完毕的答复,此时整个事务完成
结论:不管最后结果如何,第二阶段都会结束当前事务。
建议:少使用分布式事务,在分布式事务这个问题上,还很少有成熟牛逼的产品,而且分布式事务过程中,涉及到了各个节点的通知,二次通知,当节点多的时候,协调者的压力巨大,而且整个流程对业务的时间开销是巨大的,所以建议谨慎使用分布式事务,即使二阶段看似能处理好分布式节点的ACID问题,但是其本身也存在不小的问题。
1、同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
2、单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
4、二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
注:二阶段提交的缺点描述引用来自http://blog.jobbole.com/95632/。