查看原文
其他

mycat系列-Mycat里的数据库事务

2016-06-21 IT哈哈

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事务也已经满足大多数应用的需求,而且性能接近普通事务。

 Mycat 1.3目前还不支持MySQL的 Begin Transaction指令(后继会支持),而只支持set autocommit=0 & commit这种指令,对于JDBC程序来说,没有任何影响,其他语言的MySQL驱动应该也可以避免使用 Begin Transaction指令。

1.4支持Begin Transaction指令(




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.


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存