vlambda博客
学习文章列表

某东面试,被分布式事务给坑了。。。


分布式事务中,比较难的点是如何解决事务一致性问题,常见的有:

  1. 2PC(二阶段提交)
  2. 3PC(三阶段提交)
  3. TCC
  4. Saga
  5. 事务消息
  6. 最大努力通知型

本文主讲2PC。

什么是2PC?

2PC即两阶段提交,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提交阶段。

2PC中主要的2个角色:

  1. 事务协调者
  2. 事务参与者

准备阶段

事务协调者给每个事务参与者发送prepare消息,每个参在本地执行本地事务但是不要提交事务(此时事务操作的资源可能被锁定),然后给协调者返回yes或者no的消息。

提交阶段

准备阶段中所有参与者返回yes,此时事务协调者会给每个事务参与者发送commit消息,参与者接收到commit消息之后,会对本地事务执行提交操作。

若准备阶段中有参与者返回no,或者参与者响应超时(比如网络原因,导致事务协调者和事务参与者之间通讯故障),此时事务协调者会给每个事务参与者发送rollback消息,参与者接收到rollback消息之后,会对本地事务执行回滚操作。

2pc中的一些规则

  1. 阶段2 commit的条件:阶段1中所有的参与者返回yes

  2. 阶段2 rollback的条件,2种情况:阶段1中任意参与者返回no时,或者阶段1中任意参与者响应超时

  3. 当参与者prepare可以成功,那么给参与者发送commit也一定可以成功,发送rollback一定可以回滚

  4. 2PC中事务协调者这边有超时机制,即在阶段1中,协调者给参与者发送消息,一直没有回应,导致超时,此时,直接执行第二阶段rollback;而协调者这边并没有超时机器,比如所有参与者阶段1执行完毕了,然后协调者挂了,此时参与者只能一直等了,干等。

2PC 存在的问题

  1. 当阶段一都执行完毕之后,参与者本地事务执行了但是还未提交,此时参与者本地事务中的资源处于锁定状态的,若此时协调者挂了,会导致参与者本地事务锁住的资源无法释放,而直接影响到其他业务的执行。

    比如参与者1中去对商品1减库存,商品1的库存记录会被上锁,若此时其他业务也需要修改这条记录,直接会被阻塞,导致无法执行。

  2. 2PC有性能问题:比如事务中有10个参与者,参与者1在阶段1中会锁定本地资源,然后等待其他9个参与者执行完毕阶段一,然后参与者1收到事务协调器发送的commit或者rollback之后,才会释放资源,参与者1需要等待9个参与者,导致锁定资源的时间太长,会影响系统的并发量。

  3. 协调者有单点故障:当阶段1执行完毕之后,协调者挂了,此时参与者懵了,只能一直等待,这个可以通过协调者高可用来解决,后面讲到的3pc中解决了这个问题。

  4. 事务不一致的问题:阶段2中部分参与者收到了commit信息,此时协调者挂了或者网络问题,导致其他协调者无法收到commit请求,这个过程中,多个协调者中数据是不一致的,解决方式:协调者、参与者要高可用,协调者支持2PC重试,2PC中的2个阶段需要支持幂等。

XA事务

XA(eXtended Architecture)是指由X/Open 组织提出的分布式交易处理的规范。XA 是一个分布式事务协议,由Tuxedo 提出,所以分布式事务也称为XA 事务。

XA 协议主要定义了事务管理器TM(Transaction Manager,协调者)和资源管理器RM(Resource Manager,参与者)之间的接口。

其中,资源管理器往往由数据库实现,如Oracle、DB2、MySQL,这些商业数据库都实现了XA 接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

XA 事务是基于两阶段提交(Two-phaseCommit,2PC)协议实现的,可以保证数据的一致性,许多分布式关系型数据管理系统都采用此协议来完成分布式。阶段一为准备阶段,即所有的参与者准备执行事务并锁住需要的资源。当参与者Ready时,向TM 汇报自己已经准备好。阶段二为提交阶段。当TM 确认所有参与者都Ready 后,向所有参与者发送COMMIT 命令。

说的简单点:XA就是2PC在数据中的一种实现。

mysql大家都用过,普通事务过程:

start transaction; //打开事务
执行事务操作
commit|rollback; // 提交或者回滚事务

上面事务操作中,若当前连接未发送commit或者rollback操作,此时连接断掉或者mysql重启了,上面的事务会被自动回滚。

mysql中xa的语法:

XA {START|BEGIN} xid [JOIN|RESUME]   //开启XA事务,如果使用的是XA START而不是XA BEGIN,那么不支持[JOIN|RESUME],xid是一个唯一值,表示事务分支标识符
XA END xid [SUSPEND [FOR MIGRATE]]   //结束一个XA事务,不支持[SUSPEND [FOR MIGRATE]]
XA PREPARE xid 准备提交
XA COMMIT xid [ONE PHASE] //提交,如果使用了ONE PHASE,则表示使用一阶段提交。两阶段提交协议中,如果只有一个RM参与,那么可以优化为一阶段提交
XA ROLLBACK xid  //回滚
XA RECOVER [CONVERT XID]  //列出所有处于PREPARE阶段的XA事务

如:

xa start 'xa-1';
执行事务操作;
xa prepare 'xa-1'//阶段1,此时事务操作的资源被锁住,事务未提交
xa commit | rollback;//阶段2

xa事务和普通事务有点区别,上面这个xa事务有个标识xa-1,当xa-1prepare之后,如果此时连接断掉或者mysql重启了,这个事务还是处于prepare阶段,mysql重启或者调用者重新连接mysql之后,可以拿着这个事务标识xa-1继续发送xa commit |rollback来结束这个事务。

大家可以在mysql中创建几个db,然后通过上面的xa脚本试试两阶段提交,感受一下过程。

XA中事务协调器设计要点

XA中,事务参与者,比如常见的一些db,已经实现了2PC的功能,但是协调器需要自己来开发,协调器的一些设计要点:

  1. 生成全局唯一XA事务id记录,并且记录下来
  2. 事务协调器需要有重试的功能,对于中间阶段操作异常的,通过不断的重试让事务最终能够完成
  3. 协调器会有重试操作,所以需确保2pc中每个阶段都是幂等的

2PC解决方案

  1. Seata:Seata是由阿里中间件团队发起的开源项目 Fescar,后更名为Seata,它是一个是开源的分布式事务框架,这个框架支持2PC。
  2. atomikos+jta:jta是java中对分布式事务制定的接口规范,atomikos是jta的一种实现,内部是依靠XA的方式来实现的,如果事务参与者都自测XA事务,可以通过这种方式来解决,比如参与者是:mysql、oracle、sqlserver,可以使用采用这种方式;不过性能方面是值得大家考虑的一个问题。
  3. 开发者自己实现 :大家对2pc过程了解之后,可以自己开发一个,可以去挑战一下。

下一篇文章继续介绍其他的几种方式。


后面会分享更多架构相关的文章,欢迎关注我!