导读
前面已经更新完了本地事务的一些内容,本篇开始进入分布式事务的相关信息。
(本系列持续更新,感兴趣请关注我的公众号,不错过下一波干货)。
前言
本节内容主要分析下分布式事务的一些解决方案,通过图解的方式,让大家对分布式事务的执行流程有一个大致的概念,后续会通过项目来演示部分方案的使用,以及进行源码的剖析
知识点
XA事务 两段式提交(2PC) 三段式提交(3PC) TCC事务 本地消息表 可靠性消息服务 最大努力通知方案
1.XA规范
这个主要是针对单服务跨库的实现,目前很少使用,但是他的思想还是值得借鉴的。XA只是一个规范,具体的协议有数据库厂商提供实现。
这里主要涉及到几个角色,如下:
AP: 应用程序,如流量充值系统 CRM: 通信资源管理器,一般是消息队列中间件来实现 TM:事务管理器,一个第三方组件 RM:资源管理器,即mysql客户端,jdbc
1.1.2pc事务

两段式事务提交,流程如下
预提交(准备阶段):每个sql客户端,先准备执行,每个sql都确保执行没问题,只是没有提交事务而已 提交阶段: 事务管理器TM收到所有客户端的消息,若有任何一个客户端失败,则整体事务回滚,若都成功了,则每个客户端都提交事务。数据持久化
2pc事务存在的一些问题
同步阻塞:阶段一执行prepare会占用资源,一直都整个分布式事务结束才完成,在此过程中若其他服务访问该资源则会阻塞 单点故障:事务管理器(TM)是单点,若出现故障则整个事务都会出现问题 事务状态丢失:即使TM做成主备系统,那么在选举过程中如果某个程序挂了,会存在不知道某个事务的状态 脑裂问题:若在阶段2,出现异常导致某些数据库没有收到commit消息,那就会出现部分库数据提交,部分没有提交
1.2.3PC分布式事务原理
针对两段式提交协议,主要是解决了2PC协议的一些问题
CanCommit阶段:TM给各个数据发送消息,不会实际执行sql,各个库检查自己的网络环境等是否OK PreCommit阶段:若每个库的 CanCommit
都返回成功,那么就进入该阶段,执行各个SQL,只是不提交事务。若CanCommit
阶段受到失败消息,则TM通知各个数据库直接失败,结束分布式事务DoCommit阶段: 若 PreCommit
阶段都成功,那么TM发送DoCommit
消息给各个库,通知提交事务。若某个库 PreCommit
失败或超时未返回,则TM通知其他库,事务回滚若某个库返回成功,但是长时间未稍等TM通知,则直接提交事务成功
优化点
适度解决了同步阻塞问题:加入了 canCommit
,阻塞时间会缩短解决TM事务状态丢失问题:若本机数据执行成功且长时间没有TM的反馈,则自行提交事务 引入超时机制:若 PreCommit
阶段成功了,等待时间超时还未收到TM发送的DoCommit
或者Abort
消息,则自己只需DoCommit
存在问题
若TM在DoCommit
阶段发送了Abort
消息给各个库,但是某个库没有收到消息,会因为超时问题,只需DoCommit
操作。
1.3
应用实战
XA分布式事务可以通过Atomikos
类库来实现,这部分的话,有兴趣了解的同学可以网上查一下
2.TCC

由于目前的大型系统基本都是通过服务化的方式来处理,因此一般用不到上面的XA
单服务多库的方案。而TCC是在服务化中经常用到的一种分布式事务,它采用的是一种补偿
的思想。TCC是由如下3个步骤组成
Try:该阶段是对要执行服务的资源进行检查并锁定 Confirm:该阶段是对访问进行实际的操作,会将上一阶段锁定的资源变更为使用 Cancel:该节点是进行事务的回滚,即任意业务逻辑执行失败,都会将try阶段锁定的资源或者数据恢复到初始状态。
还拿事务最初的时候转账的例子来说明下,假设有一个account表,结构如下:
create table account
(
id bigint(10) unsigned auto_increment comment 'ID'
primary key,
user_account_id bigint(10) default 0 not null comment '用户账户id',
amount decimal(10, 2) unsigned default 0.00 not null comment '余额',
locked_amount decimal(10, 2) unsigned default 0.00 not null comment '冻结金额',
created_time timestamp(3) default '1971-01-01 00:00:00.000' not null comment '创建时间',
updated_time timestamp(3) default '1971-01-01 00:00:00.000' not null comment '更新时间'
)comment '账户金额' collate = utf8mb4_unicode_ci;
这里有两个账户如下,我们需要从账户user_account_id=1001
转500元到user_account_id=1002
账户中
| user_account_id | amount | locked_amount |
|---|---|---|
| 1001 | 10000 | 0 |
| 1002 | 0 | 0 |
2.1.try阶段
| user_account_id | amount | locked_amount |
|---|---|---|
| 1001 | 9500 | 500 |
| 1002 | 0 | 500 |
将 user_account_id=1001
金额减去需要转账的金额,并将锁定金额修改为要转账的金额将 user_account_id=1002
锁定金额修改为要转账的金额
此时两个账户中都有500元的锁定金额(不可用)。账户1中
2.2.confirm阶段
| user_account_id | amount | locked_amount |
|---|---|---|
| 1001 | 9500 | 500-->0 |
| 1002 | 0-->500 | 500-->0 |
若转账的业务逻辑成功,则会将账户1和账户2的金额变更上图
2.3.cancel阶段
| user_account_id | amount | locked_amount |
|---|---|---|
| 1001 | 9500-->10000 | 500-->0 |
| 1002 | 0 | 500-->0 |
若出现问题,会执行try阶段的反向操作。
使用场景
这种业务是对数据一致性要求较高的场景才会使用,必须是系统中的核心,一般都是核心自己场景才会使用。并且最好每个阶段耗时较短。
这种方式手写回滚,补偿逻辑太复杂,业务代码维护成本极高。
3.本地消息表
这个是
ebay
搞出来的一套思想,扩展及并发能力有限。因此很少使用。
这种方案依赖于每个服务的一个本地消息表,可以通过日志,数据表实现,然后再通过一定的规则去不断的重试。
有兴趣的同学可以自行研究,参考文档
本地消息表
https://houbb.github.io/2018/09/02/sql-distribute-transaction-mq
4.可靠消息最终一致性方案
这种方案是不用本地的消息表,而是直接基于MQ来处理。有一些MQ本身就只穿事务消息,如
RocketMQ
。
它的执行流程如下图:这里涉及到4个组件
A系统:事务的发起者,一般是外部请求入口 B系统:A系统依赖调用的一个服务 消息服务系统:专门用来做事务服务的一个系统,有些具备事务消息的MQ可以合并( RocketMQ
直接将消息服务
和MQ
合并)事务补偿系统:这个可以放到A系统中,通过定时任务来处理
结合上图,整体流程的执行步骤如下。
1.A系统发送一个 Prepare
消息到消息服务中2.消息服务存储该条 Prepare
消息到消息服务的库中3.消息服务返回接收消息成功给A系统 4.若步骤3成功,则A系统继续执行业务操作,否则结束 5.A执行业务逻辑成功后,通知消息系统 6.消息系统接收到消息之后,发送消息到MQ,更新消息状态 Prepared
为CONFIRMED7.MQ发送消息到B系统 8.B系统执行业务逻辑,此处B系统需要保障幂等,防止重复消息 9.B系统处理完毕之后,通知消息系统,已完成,此时消息系统修改状态为 FINISHED10.补偿机制: 定时任务去扫描消息系统的消息数据,查看非终态( Prepared
和CONFIRMED
)的消息若存在,则调用A系统的查询接口,查询数据是否处理成功 若处理成功,则再次确认并发送消息,即进入 第6步
,若失败,则删除消息。
5.最大努力通知方案
这个从名称上我们就能猜测到,他大概是需要依赖一个mq,每个服务读取到mq的消息,之后执行自己的本地事务,若失败了就不停的重试,如果重试N次不行,那就放弃。

1.A系统执行本地事务 2.本地事务执行成功,写一条消息到MQ中,然后 最大努力通知服务
会消费到该消息3.通知B系统进行业务逻辑调用,全部处理成功后才会进行消息的ACK,若又未成功的则会重试。
6.应用场景
在真实的大型分布式系统中,对于强一致性要求较高的系统一般采用TCC
方案,而其他的场景,一般是要求数据的最终一致性,采用RocketMQ
等MQ中间件来实现分布式事务。关于TCC
方案以及可靠性消息等后续会通过一个手机充值的简单例子来演示,并且会分析下一个TCC
框架的组件。
参考
本地消息表:https://houbb.github.io/2018/09/02/sql-distribute-transaction-mq 可靠性消息服务: https://ehlxr.me/2019/01/25/eventually-consistency/
【目前已经更新的内容如下】:
关于
更多内容可关注公众号
Github: https://github.com/liangliang1259/common-notes 公众号 




