Mycat高级进阶---事务支持

Mycat里的数据库事务

Mycat 目前没有出来跨分片的事务强一致性支持,目前单库内部可以保证事务的完整性,如果跨库事务, 在执行的时候任何分片出错,可以保证所有分片回滚,但是一旦应用发起commit指令,无法保证所有分片都成功,考虑到某个分片挂的可能性不大所以称为弱xa。

XA事务原理

分布式事务处理( Distributed Transaction Processing , DTP )指一个程序或程序段,在一个或多个资源如数据库或文件上为完成某些功能的执行过程的集合,分布式事务处理的关键是必须有一种方法可以知道事务在任何地方所做的所有动作,提交或回滚事务的决定必须产生统一的结果(全部提交或全部回滚)。X/Open 组织(即现在的 Open Group )定义了分布式事务处理模型。 X/Open DTP 模型( 1994 )包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通信资源管理器( CRM )四部分。一般,常见的事务管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通信资源管理器( CRM )是消息中间件,下图是X/Open DTP模型:
一般的编程方式是这样的:
Mycat高级进阶---事务支持_第1张图片

  • 配置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协议(XASpecification),指的是TM和RM之间的接口,其实这个协议只是定义了xa_和ax_系列的函数原型以及功能描述、约束和实施规范等。至于RM和TM之间通过什么协议通信,则没有提及,目前知名的数据库,如Oracle, DB2等,都是实现了XA接口的,都可以作为RM。Tuxedo、TXseries等事务中间件可以通过XA协议跟这些数据源进行对接。JTA(JavaTransaction API)是符合X/Open DTP的一个编程模型,事务管理和资源管理器支架也是用了XA协议。

下面两个图片分别给出了XA成功与失败的两种情况,首先是XA事务成功的流程图:
Mycat高级进阶---事务支持_第2张图片
然后,是XA事务失败的流程图:
Mycat高级进阶---事务支持_第3张图片
XA事务的关键在于TM组件,其中的难点技术点如下:
第二段提交时,当RM1 commit完成了,而RM2 commit还没有完成,这时TM需要进行协调,当RM2恢复以后,重新提交之前没有Commit的事务,或者自动回滚之前Rollback的事务。
因此TM需要记录XA事务的状态,以及在各个RM上的执行情况,这个日志文件需要存储在可靠的地方,用来进行XA事务异常之后的补救工作。
在The XA Specification里的2.3小节:Transaction Completion and Recovery 明确提到TM是要记录日志的:
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中,因此也就未能成功复制到备库,从而导致主备库数据不一致的情况出现。

你可能感兴趣的:(Mycat)