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

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

作者:佚名 2021-10-25 10:33:29

开发

前端

分布式 分布式事务就是指事务的发起者、资源及资源管理器和事务协调者分别位于分布式系统的不同节点之上。

 

什么是分布式事务?银行跨行转账业务是一个典型分布式事务场景,假设A需要跨行转账给B,那么就涉及两个银行的数据,无法通过一个数据库的本地事务保证转账的ACID,只能够通过分布式事务来解决。

分布式事务就是指事务的发起者、资源及资源管理器和事务协调者分别位于分布式系统的不同节点之上。在上述转账的业务中,用户A-100操作和用户B+100操作不是位于同一个节点上。本质上来说,分布式事务就是为了保证在分布式场景下,数据操作的正确执行。

什么是TCC分布式事务,TCC是Try、Confirm、Cancel三个词语的缩写,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。

TCC组成

TCC分为3个阶段

  • Try 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
  • Confirm 阶段:如果所有分支的Try都成功了,则走到Confirm阶段。Confirm真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源
  • Cancel 阶段:如果所有分支的Try有一个失败了,则走到Cancel阶段。Cancel释放 Try 阶段预留的业务资源。

TCC分布式事务里,有3个角色,与经典的XA分布式事务一样:

  • AP/应用程序,发起全局事务,定义全局事务包含哪些事务分支
  • RM/资源管理器,负责分支事务各项资源的管理
  • TM/事务管理器,负责协调全局事务的正确执行,包括Confirm,Cancel的执行,并处理网络异常

如果我们要进行一个类似于银行跨行转账的业务,转出(TransOut)和转入(TransIn)分别在不同的微服务里,一个成功完成的TCC事务典型的时序图如下:

TCC实践

对于前面的跨行转账操作,最简单的做法是,在Try阶段调整余额,在Cancel阶段反向调整余额,Confirm阶段则空操作。这么做带来的问题是,如果A扣款成功,金额转入B失败,最后回滚,把A的余额调整为初始值。在这个过程中如果A发现自己的余额被扣减了,但是收款方B迟迟没有收到余额,那么会对A造成困扰。

更好的做法是,Try阶段冻结A转账的金额,Confirm进行实际的扣款,Cancel进行资金解冻,这样用户在任何一个阶段,看到的数据都是清晰明了的。

下面我们进行一个TCC事务的具体开发

目前可用于TCC的开源框架,主要为Java语言,其中以seata为代表。我们的例子采用Python语言,使用的分布式事务框架为https://github.com/yedf/dtm,它对分布式事务的支持非常优雅。下面来详细讲解TCC的组成

我们首先创建两张表,一张是用户余额表,一张是冻结资金表,建表语句如下:

  1. CREATE TABLE dtm_busi.`user_account` ( 
  2.   `id` int(11) AUTO_INCREMENT PRIMARY KEY, 
  3.   `user_id` int(11) not NULL UNIQUE , 
  4.   `balance` decimal(10,2) NOT NULL DEFAULT '0.00'
  5.   `create_time` datetime DEFAULT now(), 
  6.   `update_time` datetime DEFAULT now() 
  7. ); 
  8.  
  9. CREATE TABLE dtm_busi.`user_account_trading` ( 
  10.   `id` int(11) AUTO_INCREMENT PRIMARY KEY, 
  11.   `user_id` int(11) not NULL UNIQUE , 
  12.   `trading_balance` decimal(10,2) NOT NULL DEFAULT '0.00'
  13.   `create_time` datetime DEFAULT now(), 
  14.   `update_time` datetime DEFAULT now() 
  15. ); 

trading表中,trading_balance记录正在交易的金额。

我们先编写核心代码,冻结/解冻资金操作,会检查约束balance+trading_balance >= 0,如果约束不成立,执行失败

  1. def tcc_adjust_trading(cursor, uid, amount): 
  2.   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)) 
  3.   if affected == 0
  4.     raise Exception("update error, maybe balance not enough"

然后是调整余额

  1. def tcc_adjust_balance(cursor, uid, amount): 
  2.   utils.sqlexec(cursor, "update dtm_busi.user_account_trading set trading_balance = trading_balance+ %d where user_id=%d" %( -amount, uid)) 
  3.   utils.sqlexec(cursor, "update dtm_busi.user_account set balance=balance+%d where user_id=%d" %(amount, uid)) 

下面我们来编写具体的Try/Confirm/Cancel的处理函数

  1. @app.post("/api/TransOutTry"
  2. def trans_out_try(): 
  3.   # 事务以及异常处理 
  4.   tcc_adjust_trading(c, out_uid, -30
  5.   return {"dtm_result""SUCCESS"
  6.  
  7. @app.post("/api/TransOutConfirm"
  8. def trans_out_confirm(): 
  9.   # 事务以及异常处理 
  10.   tcc_adjust_balance(c, out_uid, -30
  11.   return {"dtm_result""SUCCESS"
  12.  
  13. @app.post("/api/TransOutCancel"
  14. def trans_out_cancel(): 
  15.   # 事务以及异常处理 
  16.   tcc_adjust_trading(c, out_uid, 30
  17.   return {"dtm_result""SUCCESS"
  18.  
  19. @app.post("/api/TransInTry"
  20. def trans_in_try(): 
  21.   # 事务以及异常处理 
  22.   tcc_adjust_trading(c, in_uid, 30
  23.   return {"dtm_result""SUCCESS"
  24.  
  25. @app.post("/api/TransInConfirm"
  26. def trans_in_confirm(): 
  27.   # 事务以及异常处理 
  28.   tcc_adjust_balance(c, in_uid, 30
  29.   return {"dtm_result""SUCCESS"
  30.  
  31. @app.post("/api/TransInCancel"
  32. def trans_in_cancel(): 
  33.   # 事务以及异常处理 
  34.   tcc_adjust_trading(c, in_uid, -30
  35.   return {"dtm_result""SUCCESS"

到此各个子事务的处理函数已经OK了,然后是开启TCC事务,进行分支调用

  1. @app.get("/api/fireTcc"
  2. def fire_tcc(): 
  3.     # 发起tcc事务 
  4.     gid = tcc.tcc_global_transaction(dtm, utils.gen_gid(dtm), tcc_trans) 
  5.     return {"gid": gid} 
  6.  
  7. # tcc事务的具体处理 
  8. def tcc_trans(t): 
  9.     req = {"amount"30} # 业务请求的负荷 
  10.     # 调用转出服务的Try|Confirm|Cancel 
  11.     t.call_branch(req, svc + "/TransOutTry", svc + "/TransOutConfirm", svc + "/TransOutCancel"
  12.     # 调用转入服务的Try|Confirm|Cancel 
  13.     t.call_branch(req, svc + "/TransInTry", svc + "/TransInConfirm", svc + "/TransInCancel"

至此,一个完整的TCC分布式事务编写完成。

如果您想要完整运行一个成功的示例,那么按照dtmcli-py-sample项目的说明tcc的例子即可

TCC的回滚

假如银行将金额准备转入用户2时,发现用户2的账户异常,返回失败,会怎么样?我们修改代码,模拟这种情况:

  1. @app.post("/api/TransInTry"
  2. def trans_in_try(): 
  3.   # 事务以及异常处理 
  4.   tcc_adjust_trading(c, in_uid, 30
  5.   return {"dtm_result""FAILURE"

这是事务失败交互的时序图

这个跟成功的TCC差别就在于,当某个子事务返回失败后,后续就回滚全局事务,调用各个子事务的Cancel操作,保证全局事务全部回滚。

TCC网络异常

TCC在整个全局事务的过程中,可能发生各类网络异常情况,典型的是空回滚、幂等、悬挂,由于TCC的异常情况,和SAGA、可靠消息等事务模式有相近的地方,因此我们把所有异常的解决方案统统放在这篇文章分布式事务最经典的七种解决方案的异常处理章节进行讲解

小结

在这篇文章里,我们介绍了TCC的理论知识,也通过一个例子,完整给出了编写一个TCC事务的过程,涵盖了正常成功完成,以及成功回滚的情况。相信读者通过这边文章,对TCC已经有了深入的理解。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/308053.html<

(0)
管理的头像管理
上一篇2025-05-27 10:01
下一篇 2025-05-27 10:02

相关推荐

  • jsp空间购买和交换数据空间怎么买,有哪些注意事项?

    购买JSP空间时,是否考虑过数据交换空间的性能?简米科技(2003年始创,23年行业沉淀)与酷番云(工信部一类增值电信全牌照)这类持牌自营机房的服务商,能确保数据交换的高效稳定,是值得优先选择的合作伙伴,为什么JSP空间需要搭配独立的数据交换空间从JSP应用特性看数据交换需求JSP基于Java技术,常用于企业级……

    2026-08-11
    0
  • 建网站用香港空间效果怎么样,香港空间稳定吗?

    建网站用香港空间,对于创建网站资产来说,核心价值在于免备案和全球带宽优势,尤其适合外贸、跨境电商和需要快速启动的项目,但你必须权衡国内访问延迟,并选择有资质的服务商以保证资产安全,香港空间的核心优势与适用边界免备案:节省时间就是节省成本国内服务器需要备案,通常需要10到20天,香港空间无需备案,域名解析后即可上……

    2026-08-11
    0
  • Java连接云数据库的方法是什么,如何操作

    Java连接云数据库的核心在于通过JDBC驱动,结合云服务商提供的连接地址、端口、数据库名及认证信息,配置安全策略(如SSL、IP白名单),即可实现稳定高效的远程数据库访问,基础准备:JDBC驱动与依赖管理连接云数据库前,需要确保开发环境具备对应的JDBC驱动,以最常见的MySQL为例,你需要引入mysql-c……

    2026-08-11
    0
  • 建网站公安联网备案必须使用数据码吗,备案流程是什么

    网站备案包括ICP备案和公安联网备案,两者缺一不可,公安联网备案必须使用服务商提供的数据码,选择持有合法资质的服务商是顺利通过备案的前提,为什么网站必须进行公安联网备案根据公安部《计算机信息网络国际联网安全保护管理办法》,网站开通后30日内必须到公安机关办理备案手续,未完成公安备案的网站,面临责令整改、关闭网站……

    2026-08-10
    0
  • 建一个企业网站大概需要多少钱?,怎么收费?

    建网站要多少钱,没有一个固定的数字,几百到几万都可能,但真正的“创建网站资产”绝不仅仅是初次投入的成本,而是基于长期稳定、合规和安全的持续性投入,其中核心取决于你选择了什么样的“地基”来承载你的业务,建站预算的构成与行业基准当你开始规划一个网站,最先面对的就是预算问题,一个常见的误区是只关注网站“看起来”的建造……

    2026-08-10
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注