当前位置: 首页 > 后端技术 > Python

使用python轻松完成一个分布式事务TCC,保姆级教程

时间:2023-03-25 22:40:28 Python

什么是分布式事务?银行跨行转账业务是典型的分布式事务场景。假设A需要跨行给B转账,涉及到两家银行的数据。转账的ACID不能通过数据库的本地事务来保证,只能通过分布式事务来解决。分布式事务是指事务发起者、资源和资源管理者、事务协调者分别位于分布式系统的不同节点上。在上述转账业务中,用户A-100的操作和用户B+100的操作不在同一个节点上。本质上,分布式事务是为了保证分布式场景下数据操作的正确执行。什么是TCC分布式事务,TCC是Try、Confirm、Cancel这三个词的缩写,最早是由PatHelland在2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出的。TCC的组成TCC分为3个阶段Try阶段:try执行,完成所有业务检查(一致性),预留必要的业务资源(准隔离)Confirm阶段:如果Try的所有分支都成功,则进入Confirm阶段。Confirm实际执行业务,不做任何业务检查,只使用Try阶段预留的业务资源。Cancel阶段:如果所有分支中有一个Try失败,则进入Cancel阶段。Cancel释放Try阶段预留的业务资源。在TCC分布式事务中,有3个角色,与经典的XA分布式事务相同:AP/应用程序,发起全局事务,定义全局事务中包含哪些事务分支RM/资源管理器,负责分支交易各种资源的管理TM/transactionmanager负责协调全局交易的正确执行,包括Confirm和Cancel的执行,以及处理网络异常。如果我们要进行类似银行跨行转账的业务,分别转出(TransOut)和转入(TransIn),在不同的微服务中,成功完成TCC交易的典型时序图如下:跨行转账操作,最简单的方法是在Try阶段调整余额,在Cancel阶段反转余额调整,确认阶段然后no-op。这样造成的问题是,如果A扣钱成功,但是转账给B失败,就会回滚,A的余额会调整到初始值。在这个过程中,如果A发现自己的余额被扣了,而收款人B却迟迟没有收到余额,就会给A带来麻烦。更好的做法是在Try阶段冻结A转账的金额,确认实际扣款,取消解冻资金,让用户在任何阶段看到的数据都清晰明了。接下来,我们将对TCC事务进行具体的开发。TCC目前可用的开源框架主要是Java语言,其中seata是代表。我们的例子使用的是Python语言,使用的分布式事务框架是https://github.com/yedf/dtm,非常优雅的支持分布式事务。下面详细解释一下TCC的组成。我们先创建两张表,一张是用户余额表,一张是冻结资金表。建表语句如下:CREATETABLEdtm_busi.`user_account`(`id`int(11)AUTO_INCREMENTPRIMARYKEY,`user_id`int(11)notNULLUNIQUE,`balance`decimal(10,2)NOTNULLDEFAULT'0.00',`create_time`datetimeDEFAULTnow(),`update_time`datetimeDEFAULTnow());CREATETABLEdtm_busi.`user_account_trading`(`id`int(11)AUTO_INCREMENTPRIMARYKEY,`user_id`int(11)notNULLUNIQUE,`trading_balance`decimal(10,2)NOTNULLDEFAULT'0.00',`create_time`datetimeDEFAULTnow(),`update_time`datetimeDEFAULTnow());在交易表中,trading_balance记录了正在交易的金额。先写核心代码,冻结/解冻资金操作,检查约束balance+trading_balance>=0,如果约束不成立则执行失败deftcc_adjust_trading(cursor,uid,amount):affected=utils.sqlexec(光标,“更新dtm_busi.user_account_tradingsettrading_balance=trading_balance+%dwhereuser_id=%dandtrading_balance+%d+(selectbalancefromdtm_busi.user_accountwhereid=%d)>=0"%(amount,uid,amount,uid))ifaffected==0:raiseException("updateerror,maybebalancenotenough")然后调整余额deftcc_adjust_balance(cursor,uid,amount):utils.sqlexec(cursor,"updatedtm_busi.user_account_tradingsettrading_balance=trading_balance+%dwhereuser_id=%d"%(-amount,uid))utils.sqlexec(cursor,"updatedtm_busi.user_accountsetbalance=balance+%dwhereuser_id=%d"%(amount,uid))让我们写一个具体的Try/Confirm/Cancel处理函数@app.post("/api/TransOutTry")deftrans_out_try():#transaction和异常处理tcc_adjust_trading(c,out_uid,-30)return{"dtm_result":"SUCCESS"}@app.post("/api/TransOutConfirm")deftrans_out_confirm():#事务和异常处理tcc_adjust_balance(c,out_uid,-30)return{"dtm_result":"SUCCESS"}@app.post("/api/TransOutCancel")deftrans_out_cancel():#事务和异常处理tcc_adjust_trading(c,out_uid,30)return{"dtm_result":"SUCCESS"}@app.post("/api/TransInTry")deftrans_in_try():#交易和异常处理tcc_adjust_trading(c,in_uid,30)return{"dtm_result":"SUCCESS"}@app.post("/api/TransInConfirm")deftrans_in_confirm():#事务和异常处理tcc_adjust_balance(c,in_uid,30)return{"dtm_result":"SUCCESS"}@app.post("/api/TransInCancel")deftrans_in_cancel():#交易和异常处理tcc_adjust_trading(c,in_uid,-30)return{"dtm_result":"SUCCESS"}至此,各个子交易的处理函数已经OK了,然后开始TCC事务,并进行分支调用@app.get("/api/fireTcc")deffire_tcc():#发起一个tcc事务gid=tcc.tcc_global_transaction(dtm,utils.gen_gid(dtm),tcc_trans)return{"gid":gid}#tcc交易的具体处理deftcc_trans(t):req={"amount":30}#业务请求加载#调用转账服务t的Try|Confirm|Cancel.call_branch(req,svc+"/TransOutTry",svc+"/TransOutConfirm",svc+"/TransOutCancel")#调用转移服务的Try|Confirm|Cancelt.call_branch(req,svc+"/TransInTry",svc+"/TransInConfirm",svc+"/TransInCancel")所以至此,一个完整的TCC分布式事务就写完了,如果想完整运行一个成功的例子,那么按照dtmcli-py-sample项目的说明,tcc例子可以回滚TCC,如果银行转账给用户2、用户2的账户出现异常,返回失败怎么办?我们修改一下代码,模拟这种情况:@app.post("/api/TransInTry")deftrans_in_try():#交易和异常处理tcc_adjust_trading(c,in_uid,30)return{"dtm_result":"FAILURE"}这是事务失败交互的时序图,这个和成功的TCC的区别在于,当一个子事务失败时,全局事务随后回滚,并且每个子交易都调用Cancel操作,保证所有的全局t交易被回滚。TCC网络异常TCC在整个全局事务过程中可能会出现各种网络异常,典型的是空回滚、幂等、挂起,由于TCC异常,与SAGA、Reliablemessages等事务模式类似,所以我们把所有的异常解决方案放在异常处理章节对本文最经典的七大分布式事务解决方案进行讲解总结。在这篇文章中,我们介绍了TCC的理论知识,也通过一个例子给出了,完整的给出了编写一个TCC事务的过程,涵盖了正常成功完成和成功回滚的情况。相信读者通过本文对TCC有了更深入的了解,关于分布式事务更全面的知识,请参考分布式事务最经典的七大解决方案。本文所用示例摘自yedf/dtm,支持多种事务模式:TCC、SAGA、XA,事务消息跨语言支持,已支持golang、python、PHP、nodejs、Java等多种语言的客户端。提供子事务屏障功能,优雅解决幂等、挂起、空值补偿等问题。看完这篇干货,欢迎大家访问https://github.com/yedf/dtm项目,给star支持!