一、
XA事务原理
分布式事务处理( Distributed Transaction Processing , DTP )指一个程序或程序段,在一个或多个资源如数据库或文件上为完成某些功能的执行过程的集合,分布式事务处理的关键是必须有一种方法可以知道事务在任何地方所做的所有动作,提交或回滚事务的决定必须产生统一的结果(全部提交或全部回滚)。X/Open 组织(即现在的 Open Group )定义了分布式事务处理模型。 X/Open DTP 模型( 1994 )包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通信资源管理器( CRM )四部分。一般,常见的事务管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通信资源管理器( CRM )是消息中间件,下图是X/Open DTP模型:
一般的编程方式是这样的:
配置TM,通过TM或者RM提供的方式,把RM注册到TM。可以理解为给TM注册RM作为数据源。一个TM可以注册多个RM。
AP从TM获取资源管理器的代理(例如:使用JTA接口,从TM管理的上下文中,获取出这个TM所管理的RM的JDBC连接或JMS连接)。
AP向TM发起一个全局事务。这时,TM会通知各个RM。XID(全局事务ID)会通知到各个RM。
AP通过1中获取的连接,直接操作RM进行业务操作。这时,AP在每次操作时把XID(包括所属分支的信息)传递给RM,RM正是通过这个XID与2步中的XID关联来知道操作和事务的关系的。
AP结束全局事务。此时TM会通知RM全局事务结束。
开始二段提交,也就是prepare - commit的过程。
XA协议(XA Specification),指的是TM和RM之间的接口,其实这个协议只是定义了xa_和ax_系列的函数原型以及功能描述、约束和实施规范等。至于RM和TM之间通过什么协议通信,则没有提及,目前知名的数据库,如Oracle, DB2等,都是实现了XA接口的,都可以作为RM。Tuxedo、TXseries等事务中间件可以通过XA协议跟这些数据源进行对接。JTA(Java Transaction API)是符合X/Open DTP的一个编程模型,事务管理和资源管理器支架也是用了XA协议。
下面两个图片分别给出了XA成功与失败的两种情况,首先是XA事务成功的流程图:
然后,是XA事务失败的流程图:
XA事务的关键在于TM组件,其中的难点技术点如下:
第二段提交时,当RM1 commit完成了,而RM2 commit还没有完成,这时TM需要进行协调,当RM2恢复以后,
重新提交
之前没有Commit的事务,或者自动回滚之前Rollback的事务。
因此
TM需要记录XA事务的状态
,以及在各个
RM上的执行情况
,这个
日志文件
需要
存储在可靠
的地方,用来进行XA事务异常之后的补救工作。
在The XA Specification里的2.3小节:Transaction Completion and Recovery 明确提到TM是要记录日志的:
In Phase 2, the TM issues all RMs an actual request to commit or roll back the transaction branch, as the case may be. (Before issuing requests to commit, the TM stably records the fact that it decided to commit, as well as a list of all involved RMs.) All RMs commit or roll back changes to shared resources and then return status to the TM. The TM can then discard its knowledge of the global transaction.
TM是一定要把事务的信息,比如XID,哪个RM已经完成了等保存起来的。只有当全部的RM提交或者回滚完后,才能丢弃这些事务的信息。
于是我们明白
TM是一个单点
,要非常可靠才行。
以Java分布式事务的开源TM组件atomikos为例,它是通过在应用的目录下生成日志文件来保证,如果失败,在重启后可以通过日志来完成未完成的事务。
Mycat未来计划以Zookeeper作为XA事务的日志存储手段,实现TM角色以支持XA事务.
二、XA事务的问题和MySQL的局限
XA事务的明显问题是timeout问题,比如当一个RM出问题了,那么整个事务只能处于等待状态。这样可以会连锁反应,导致整个系统都很慢,最终不可用,另外2阶段提交也大大增加了XA事务的时间,使得XA事务无法支持高并发请求。
避免使用XA事务的方法通常是最终一致性。
举个例子,比如一个业务逻辑中,最后一步是用户账号增加300元,为了减少DB的压力,先把这个放到消息队列里,然后后端再从消息队列里取出消息,更新DB。那么如何保证,这条消息不会被重复消费?或者重复消费后,仍能保证结果是正确的?在消息里带上用户帐号在数据库里的版本,在更新时比较数据的版本,如果相同则加上300;比如用户本来有500元,那么消息是更新用户的钱数为800,而不是加上300;
另外一个方式是,建一个消息是否被消费的表,记录消息ID,在事务里,先判断消息是否已经消费过,如果没有,则更新数据库,加上300,否则说明已经消费过了,丢弃。
前面两种方法都必须从流程上保证是单方向的。
其实严格意义上,用消息队列来实现最终一致性仍然有漏洞,因为消息队列跟当前操作的数据库是两个不同的资源,仍然存在消息队列失败导致这个账号增加300元的消息没有被存储起来(当然复杂的高级的消息队列产品可以避免这种现象,但仍然存在风险),而第二种方式则由于
新的表跟之前的事务操作的表示在一个Database中
,因此不存在上述的可能性。
MySQL的XA事务,长期以来都存在一个缺陷:
MySQL数据库的主备数据库的同步,通过Binlog的复制完成。而Binlog是MySQL数据库内部XA事务的协调者,并且MySQL数据库为binlog做了优化——
binlog不写prepare日志
,
只写commit日志
。所有的参与节点prepare完成,在进行xa commit前crash。crash recover如果选择commit此事务。由于binlog在prepare阶段未写,因此主库中看来,此分布式事务最终提交了,但是此事务的操作并未写到binlog中,因此也就未能成功复制到备库,从而导致
主备库数据不一致的情况出现
。
Prior to MySQL 5.7.7, XA transactions were not compatible with replication. This was because an XA transaction that was in PREPARED state would be rolled back on clean server shutdown or client disconnect. Similarly, an XA transaction that was in PREPARED state would still exist in PREPARED state in case the server was shutdown abnormally and then started again, but the contents of the transaction could not be written to the binary log. In both of these situations the XA transaction could not be replicated correctly.
In MySQL 5.7.7 and later, there is a change in behavior and an XA transaction is written to the binary log in two parts. When XA PREPARE is issued, the first part of the transaction up to XA PREPARE is written using an initial GTID. A XA_prepare_log_event is used to identify such transactions in the binary log. When XA COMMIT or XA ROLLBACK is issued, a second part of the transaction containing only the XA COMMIT or XA ROLLBACK statement is written using a second GTID. Note that the initial part of the transaction, identified by XA_prepare_log_event, is not necessarily followed by its XA COMMIT or XA ROLLBACK, which can cause interleaved binary logging of any two XA transactions. The two parts of the XA transaction can even appear in different binary log files. This means that an XA transaction in PREPARED state is now persistent until an explicit XA COMMIT or XA ROLLBACK statement is issued, ensuring that XA transactions are compatible with replication.
二、MyCAT分布式事务
Mycat里的事务包括以下几种情况:
SQL不垮分片:事务中的SQL在单个节点上执行
SQL跨分片:事务中的SQL在多个节点上执行
其中,第一种情况,SQL仅仅在一个dataNode上执行,此时Mycat事务模式跟标准的数据库事务模式一样,要么提交要么回滚;而对于第二种和第三种的事务,Mycat执行的一种”弱XA事务“模式,此模式的逻辑如下:
首先事务内的SQL在各自的分片上执行并返回状态码,若某个分片上的返回码为ERROR,则Mycat认为事务失败,应用端只能回滚(rollback)事务,Mycat收到回滚指令后,依次回滚事务中涉及到的所有分片;若事务中的所有SQL的执行都返回成功(OK)的返回码,则应用程序提交事务的时候,Mycat会同时向事务中涉及到的节点发送提交事务的指令。
举例如下:
客户端执行如下的指令:
set autocommit=0
update person set name=‘xxxx’ where age >18
commit
如果person表跨分片(dn1,dn2,dn3),则上述SQL将触发如下的执行逻辑
for( dn1,dn2,dn3){
set autocommit=0;
update person set name='xxxx' where age >18;
if(allOK){
for(dn1,dn2,dn3){
commit;
这里称之为弱XA,是因为第二阶段Commit的时候,若某个节点出错了,也无法等节点恢复以后去做Recover操作重新commit(只能回滚,而不会等其恢复后再次提交。),但考虑到所有的节点都执行成功,但Commit指令失败的概率很小,因此这种弱XA事务也已经满足大多数应用的需求,而且性能接近普通事务。
XA本质上还是一个二阶段提交,二阶段提交看起来确实能够提供原子性的操作,但是不幸的事,二阶段提交还是有几个缺点的:
1、同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
2、单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
4、二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
四、参考资料
http://www.wxzhi.com/archives/765/rjs6w5dex0glaqob/
转:https://my.oschina.net/fileoptions/blog/888207