TCC分布式事务来源于 2007 年Pat Helland发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文,TCC分别是Try、Confirm、Cancel的手写字母。
组成
TCC有三个分支
-
Try 分支:预留锁定业务相关资源,如果资源不够,则返回失败
-
Confirm 分支:如果前面的Try全部成功,则进入Confirm,进行数据变更,这个阶段不会返回失败
-
Cancel 分支:如果前面的Try没有全部成功,有返回失败的,则进入Cancel。Cancel解冻Try锁定的资源,也类似Confirm是不会返回失败的。
与经典的XA分布式事务一样,Tcc事务也可以分为下面三个角色:
-
AP/应用程序,发起全局事务,定义全局事务包含哪些事务分支
-
RM/资源管理器,负责分支事务各项资源的管理
-
TM/事务管理器,负责协调全局事务的正确执行,包括Confirm,Cancel的执行,并处理网络异常
如果我们要进行一个类似于银行跨行转账的业务,转出(TransOut)和转入(TransIn)分别在不同的微服务里,一个成功完成的TCC事务典型的时序图如下:
TCC 实践
A转账给B的跨行转账操作,如果转账不成功,我们不想让用户看到自己账上的余额变动过,因此我们在Try阶段冻结相关的余额,Confirm阶段进行转账,Cancel阶段进行余额解冻。这样可以避免A看到自己的存款减少了,但是最后转账又失败的情况。
下面是具体的开发详情
我们采用Python语言,使用https://github.com/yedf/dtm这个功能强大又简单易用的分布式事务框架
创建两张表,一个用户余额表,另一个是冻结资金表,语句如下:
CREATE TABLE dtm_busi.`user_account` (
`id` int(11) AUTO_INCREMENT PRIMARY KEY,
`user_id` int(11) not NULL UNIQUE ,
`balance` decimal(10,2) NOT NULL DEFAULT '0.00',
`create_time` datetime DEFAULT now(),
`update_time` datetime DEFAULT now()
);
CREATE TABLE dtm_busi.`user_account_trading` (
`id` int(11) AUTO_INCREMENT PRIMARY KEY,
`user_id` int(11) not NULL UNIQUE ,
`trading_balance` decimal(10,2) NOT NULL DEFAULT '0.00',
`create_time` datetime DEFAULT now(),
`update_time` datetime DEFAULT now()
);
trading 表中,trading_balance 记录正在交易的金额。
我们先编写核心代码,冻结 /解冻资金操作,会检查约束 balance+trading_balance >= 0,如果约束不成立,执行失败
def tcc_adjust_trading(cursor, uid, amount):
affected = utils.sqlexec(cursor, "update dtm_busi.user_account_trading set trading_balance=trading_balance + %d where user_id=%d and trading_balance + %d + (select balance from dtm_busi.user_account where id=%d) >= 0" % (amount, uid, amount, uid))
if affected == 0:
raise Exception("update error, maybe balance not enough")
然后是调整余额
def tcc_adjust_balance(cursor, uid, amount):
utils.sqlexec(cursor, "update dtm_busi.user_account_trading set trading_balance = trading_balance+ %d where user_id=%d" %( -amount, uid))
utils.sqlexec(cursor, "update dtm_busi.user_account set balance=balance+%d where user_id=%d" %(amount, uid))
下面我们来编写具体的 Try/Confirm/Cancel 的处理函数
@app.post("/api/TransOutTry")
def trans_out_try():
# 事务以及异常处理
tcc_adjust_trading(c, out_uid, -30)
return {"dtm_result": "SUCCESS"}
@app.post("/api/TransOutConfirm")
def trans_out_confirm():
# 事务以及异常处理
tcc_adjust_balance(c, out_uid, -30)
return {"dtm_result": "SUCCESS"}
@app.post("/api/TransOutCancel")
def trans_out_cancel():
# 事务以及异常处理
tcc_adjust_trading(c, out_uid, 30)
return {"dtm_result": "SUCCESS"}
@app.post("/api/TransInTry")
def trans_in_try():
# 事务以及异常处理
tcc_adjust_trading(c, in_uid, 30)
return {"dtm_result": "SUCCESS"}
@app.post("/api/TransInConfirm")
def trans_in_confirm():
# 事务以及异常处理
tcc_adjust_balance(c, in_uid, 30)
return {"dtm_result": "SUCCESS"}
@app.post("/api/TransInCancel")
def trans_in_cancel():
# 事务以及异常处理
tcc_adjust_trading(c, in_uid, -30)
return {"dtm_result": "SUCCESS"}
到此各个子事务的处理函数已经 OK 了,然后是开启 TCC 事务,进行分支调用
@app.get("/api/fireTcc")
def fire_tcc():
# 发起 tcc 事务
gid = tcc.tcc_global_transaction(dtm, utils.gen_gid(dtm), tcc_trans)
return {"gid": gid}
# tcc 事务的具体处理
def tcc_trans(t):
req = {"amount": 30} # 业务请求的负荷
# 调用转出服务的 Try|Confirm|Cancel
t.call_branch(req, svc + "/TransOutTry", svc + "/TransOutConfirm", svc + "/TransOutCancel")
# 调用转入服务的 Try|Confirm|Cancel
t.call_branch(req, svc + "/TransInTry", svc + "/TransInConfirm", svc + "/TransInCancel")
至此,一个完整的 TCC 分布式事务编写完成。
如果您想要完整运行一个成功的示例,那么按照 dtmcli-py-sample 项目的说明运行 tcc 的例子即可
TCC 的回滚
假如银行将金额准备转入用户 2 时,发现用户 2 的账户异常,返回失败,会怎么样?我们修改代码,模拟这种情况:
@app.post("/api/TransInTry")
def trans_in_try():
# 事务以及异常处理
tcc_adjust_trading(c, in_uid, 30)
return {"dtm_result": "FAILURE"}
这是事务失败交互的时序图
这个跟成功的 TCC 差别就在于,当某个子事务返回失败后,后续就回滚全局事务,调用各个子事务的 Cancel 操作,保证全局事务全部回滚。
TCC 网络异常
TCC 在整个全局事务的过程中,可能发生各类网络异常情况,典型的是空回滚、幂等、悬挂,由于 TCC 的异常情况,和 SAGA 、可靠消息等事务模式有相近的地方,因此我们把所有异常的解决方案统统放在这篇文章分布式事务最经典的七种解决方案的异常处理章节进行讲解
小结
在这篇文章里,我们介绍了 TCC 的理论知识,也通过一个例子,完整给出了编写一个 TCC 事务的过程,涵盖了正常成功完成,以及成功回滚的情况。相信读者通过这边文章,对 TCC 已经有了深入的理解。
关于分布式事务更多更全面的知识,请参考分布式事务最经典的七种解决方案
文中使用的例子节选自github.com/yedf/dtm,该框架功能强大又简单易用。
-
支持多种事务模式:TCC、SAGA、XA、事务消息;
-
跨语言支持,已支持 golang、python、PHP、nodejs、Java等语言的客户端。
-
提供子事务屏障功能,优雅解决幂等、悬挂、空补偿等问题。
如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,
关注与私信博主(222)学习更多Python知识与技巧,课件,源码,安装包,还有最新大厂面试资料等等等
咱们下期见。
收藏 等于白嫖,点赞才是真情。