dtm/intra-barrier.md
2021-07-03 11:44:01 +08:00

6.6 KiB
Raw Blame History

子事务屏障--彻底解决子事务乱序执行问题

子事务乱序问题

我们拿分布式事务中的TCC作为例子看看如何子事务乱序是什么样的如何解决。 假设一个分布式TCC事务G包含子事务A和B全部成功为 A-Try ok -> B-Try ok -> G完成 当网络出现问题时,下面是某一种常见情形 A-Try 网络丢包导致超时 -> A Cancel ok丢包 -> A Try 请求到达子事务进行处理此时因为Cancel已成功需要忽略立即返回)->A Cancel ok 这种情况下: A Cancel在Try之前执行称为空补偿问题此时Cancel的处理需要进行空补偿称为空补偿控制 A 第二次Cancel称为幂等问题此时Cancel的处理需要判断此前已回滚此次忽略成为幂等控制 A 第二次Try时之前已进行了Cancel此次之行应当忽略我们称之为防悬挂控制 这几种情况的正确处理,通常需要子事务做精细处理,例如记录业务处理的主键,并保证上述描述的逻辑。

子事务屏障提供了一套全新方法,让业务编写者完全不用被上述问题困扰,他的工作方式如下:

子事务进入屏障在屏障中编写自己的业务逻辑由屏障保证内部Try-Confirm-Cancel逻辑只被提交一次

子事务屏障的原理如下: 它使用表sub_trans_barrier主键为全局事务id-子事务id-子事务分支名称try|confirm|cancel 。开启事务 。如果是Try分支则那么insert ignore插入gid-branchid-try如果成功插入则调用屏障内逻辑 . 如果是Confirm分支那么insert ignore插入gid-branchid-confirm如果成功插入则调用屏障内逻辑 . 如果是Cancel分支那么insert ignore插入gid-branchid-try再插入gid-branchid-cancel如果try未插入并且cancel插入成功则调用屏障内逻辑 屏障内如果调用成功,提交事务,返回成功 如果调用异常,回滚事务,返回异常

在此机制下,解决了乱序问题 空补偿控制--如果Try没有执行直接执行了Cancel那么Cancel插入gid-branchid-try会成功不走屏障内的逻辑保证了空补偿控制 幂等控制--任何一个分支都无法重复插入唯一键,保证了不会重复执行 防悬挂控制--Try在Cancel之后执行那么插入的gid-branchid-try不成功就不执行保证了防悬挂控制

通过子事务屏障,完全解决了子事务乱序问题,业务人员可以只关心自己的业务逻辑

某些业务要求,一系列操作必须全部执行,而不能仅执行一部分。例如,一个转账操作:

-- 从id=1的账户给id=2的账户转账100元
-- 第一步将id=1的A账户余额减去100
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 第二步将id=2的B账户余额加上100
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

这两条SQL语句必须全部执行或者由于某些原因如果第一条语句成功第二条语句失败就必须全部撤销。

这种把多条语句作为一个整体进行操作的功能被称为数据库事务。数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。如果事务失败那么效果就和没有执行这些SQL一样不会对数据库数据有任何改动。

更多事务介绍

微服务

如果一个事务涉及的所有操作能够放在一个服务内部,那么使用各门语言里事务相关的库,可以轻松的实现多个操作作为整体的事务操作。

但是有些服务,例如生成订单涉及做很多操作,包括库存、优惠券、赠送、账户余额等。当系统复杂程度增加时,想要把所有这些操作放到一个服务内实现,会导致耦合度太高,维护成本非常高。

针对复杂的系统,当前流行的微服务架构是非常好的解决方案,该架构能够把复杂系统进行拆分,拆分后形成了大量微服务,独立开发,独立维护。

更多微服务介绍

虽然服务拆分了,但是订单本身的逻辑需要多个操作作为一个整体,要么全部成功,要么全部失败,这就带来了新的挑战。如何把散落在各个微服务中的本地事务,组成一个大的事务,保证他们作为一个整体,这就是分布式事务需要解决的问题。

分布式事务

分布式事务简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

更多分布式事务介绍

分布式事务方案包括:

  • xa
  • tcc
  • saga
  • 可靠消息

下面我们看看最简单的xa

XA

XA一共分为两阶段

第一阶段prepare事务管理器向所有本地资源管理器发起请求询问是否是 ready 状态,所有参与者都将本事务能否成功的信息反馈发给协调者;

第二阶段 (commit/rollback):事务管理器根据所有本地资源管理器的反馈,通知所有本地资源管理器,步调一致地在所有分支上提交或者回滚。

目前主流的数据库基本都支持XA事务包括mysql、oracle、sqlserver、postgre

xa实践

介绍了这么多我们来实践完成一个微服务上的xa事务加深分布式事务的理解这里将采用dtm作为示例

安装go

安装mysql

获取dtm

git clone https://github.com/yedf/dtm.git
cd dtm

配置mysql

cp conf.sample.yml conf.yml
vi conf.yml

运行示例

go run app/main.go xa

从日志里,能够找到以下输出

# 服务1输出
XA start '4fPqCNTYeSG'
UPDATE `user_account` SET `balance`=balance + 30,`update_time`='2021-06-09 11:50:42.438' WHERE user_id = '1'
XA end '4fPqCNTYeSG'
XA prepare '4fPqCNTYeSG'

# 服务2输出
XA start '4fPqCPijxyC'
UPDATE `user_account` SET `balance`=balance - 30,`update_time`='2021-06-09 11:50:42.493' WHERE user_id = '2'
XA end '4fPqCPijxyC'
XA prepare '4fPqCPijxyC'

# 服务1输出
xa commit '4fPqCNTYeSG'

#服务2输出
xa commit '4fPqCPijxyC'

总结

在这篇简短的文章里,我们大致介绍了 事务->分布式事务->微服务处理XA事务。有兴趣的同学可以通过dtm继续研究分布式事务