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

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知识与技巧,课件,源码,安装包,还有最新大厂面试资料等等等
咱们下期见。
收藏 等于白嫖,点赞才是真情。
 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值