17

What is TCC? TCC is the abbreviation of Try, Confirm, and Cancel. It was first proposed by Pat Helland in a paper titled "Life beyond Distributed Transactions: an Apostate's Opinion" published in 2007.

Composition of TCC

TCC is divided into 3 stages

  • Try phase: try to execute, complete all business checks (consistency), reserve necessary business resources (quasi-isolation)
  • Confirm stage: If the Try of all branches is successful, go to the Confirm stage. Confirm actually executes the business, does not perform any business checks, and only uses the business resources reserved in the Try phase
  • Cancel stage: If one of the Try of all branches fails, go to the Cancel stage. Cancel releases the business resources reserved in the Try phase.

In the TCC distributed transaction, there are 3 roles, which are the same as the classic XA distributed transaction:

  • AP/application, initiate a global transaction, and define which transaction branches are included in the global transaction
  • RM/resource manager, responsible for the management of various resources of branch transactions
  • TM/transaction manager, responsible for coordinating the correct execution of global transactions, including the execution of Confirm, Cancel, and handling network exceptions

If we want to conduct a business similar to inter-bank transfer between banks, the transfer out (TransOut) and the transfer in (TransIn) are in different microservices, and a typical sequence diagram of a successfully completed TCC transaction is as follows:
image.png

TCC network exception

During the entire global transaction process of TCC, various network exceptions may occur, typically empty rollback, idempotency, and suspension. Due to the exception of TCC, it is similar to SAGA, reliable message and other transaction modes, so we Put all the abnormal solutions in this article "Still troubled by network exceptions of distributed transactions? A function call will help you get it done" to explain

TCC Practice

For the previous inter-bank transfer operation, the easiest way is to adjust the balance in the Try phase, reversely adjust the balance in the Cancel phase, and perform no operation in the Confirm phase. The problem caused by this is that if A successfully deducts the money, the transfer of the amount to B fails, and finally rolls back and adjusts the balance of A to the initial value. In this process, if A finds that his balance has been deducted, but the payee B has not received the balance for a long time, it will cause trouble to A.

A better approach is to freeze the amount transferred by A in the Try phase, confirm the actual deduction, and Cancel to unfreeze the funds, so that the user can see the data clearly and clearly at any stage.

Next, we will carry out the specific development of a TCC transaction

The open source framework currently available for TCC is mainly the Java language, represented by seata. Our example uses the go language, and the distributed transaction framework used is https://github.com/dtm-labs/dtm , which supports distributed transactions very elegantly. The following is a detailed explanation of the composition of TCC

We first create two tables, one is the user balance table and the other is the frozen funds table. The table creation statement is as follows:

create table if not exists dtm_busi.user_account(
  id int(11) PRIMARY KEY AUTO_INCREMENT,
  user_id int(11) UNIQUE,
  balance DECIMAL(10, 2) not null default '0',
  trading_balance DECIMAL(10, 2) not null default '0',
  create_time datetime DEFAULT now(),
  update_time datetime DEFAULT now(),
  key(create_time),
  key(update_time)
);

trading_balance records the amount being traded.

We first write the core code, freeze/unfreeze funds operation, will check the constraint balance+trading_balance >= 0, if the constraint is not established, the execution fails

func adjustTrading(uid int, amount int) (interface{}, error) {
  幂等、悬挂处理
  dbr := sdb.Exec(`update dtm_busi.user_account set trading_balance=trading_balance+? where user_id=? and trading_balance + ? + balance >= 0`, amount, uid, amount)
  if dbr.Error == nil && dbr.RowsAffected == 0 { // 如果余额不足,返回错误
    return nil, fmt.Errorf("update error, balance not enough")
  }
  其他情况检查及处理
}

Then adjust the balance

func adjustBalance(uid int, amount int) (ret interface{}, rerr error) {
  幂等、悬挂处理
  这里略去进行相关的事务处理,包括开启事务,以及在defer中处理提交或回滚
  // 将原先冻结的资金记录解冻
  dbr := db.Exec(`update dtm_busi.user_account set trading_balance=trading_balance-?, balance=balance+? where user_id=?`, amount, amount, uid)
  其他情况检查及处理
}

Let's write a specific Try/Confirm/Cancel processing function

RegisterPost(app, "/api/TransInTry", func (c *gin.Context) (interface{}, error) {
  return adjustTrading(1, reqFrom(c).Amount)
})
RegisterPost(app, "/api/TransInConfirm", func TransInConfirm(c *gin.Context) (interface{}, error) {
  return adjustBalance(1, reqFrom(c).Amount)
})
RegisterPost(app, "/api/TransInCancel", func TransInCancel(c *gin.Context) (interface{}, error) {
  return adjustTrading(1, -reqFrom(c).Amount)
})

RegisterPost(app, "/api/TransOutTry", func TransOutTry(c *gin.Context) (interface{}, error) {
  return adjustTrading(2, -reqFrom(c).Amount)
})
RegisterPost(app, "/api/TransOutConfirm", func TransInConfirm(c *gin.Context) (interface{}, error) {
  return adjustBalance(2, -reqFrom(c).Amount)
})
RegisterPost(app, "/api/TransOutCancel", func TransInCancel(c *gin.Context) (interface{}, error) {
  return adjustTrading(2, reqFrom(c).Amount)
})

At this point, the processing functions of each sub-transaction have been OK, and then the TCC transaction is opened and the branch call is made.

// TccGlobalTransaction 会开启一个全局事务
_, err := dtmcli.TccGlobalTransaction(DtmServer, func(tcc *dtmcli.Tcc) (rerr error) {
  // CallBranch 会将事务分支的Confirm/Cancel注册到全局事务上,然后直接调用Try
  res1, rerr := tcc.CallBranch(&TransReq{Amount: 30}, host+"/api/TransOutTry", host+"/api/TransOutConfirm", host+"/api/TransOutRevert"
  进行错误检查,以及其他逻辑
  res2, rerr := tcc.CallBranch(&TransReq{Amount: 30}, host+"/api/TransInTry", host+"/api/TransInConfirm", host+"/api/TransInRevert")
  进行错误检查,有任何错误,返回错误,回滚交易
  // 如果没有错误,函数正常返回后,全局事务会提交,TM会调用各个事务分支的Confirm,完成整个事务
})

So far, a complete TCC distributed transaction has been written.

If you want to run a successful example, after setting up the environment according to the instructions of the dtm project, under the project [dtm-labs/dtm-examples], run the following command to run the tcc example

go run main.go tcc_barrier

Rollback of TCC

What if the bank finds that the account of user 2 is abnormal and fails when the bank transfers the amount to user 2? We modify the code to simulate this situation:

RegisterPost(app, "/api/TransInTry", func (c *gin.Context) (interface{}, error) {
  return gin.H{"dtm_result":"FAILURE"}, nil
})

This is the sequence diagram of the transaction failure interaction
image.png

The difference between this and a successful TCC is that when a sub-transaction fails, the global transaction is rolled back, and the Cancel operation of each sub-transaction is called to ensure that all global transactions are rolled back.

summary

In this article, we introduce the theoretical knowledge of TCC, and also give a complete process of writing a TCC transaction through an example, covering the normal and successful completion and successful rollback. I believe that readers have a deep understanding of TCC through this article.

For the idempotent, suspension, and null compensation that need to be dealt with in distributed transactions, please refer to another article: Distributed transactions you must not know the pit, a function call can help you solve it

For more and more comprehensive knowledge of distributed transactions, please refer to most classic seven solutions for distributed

The examples used in this article are excerpted from dtm-labs/dtm , which supports multiple transaction modes: TCC, SAGA, XA, transaction message cross-language support, and supports clients in languages such as golang, python, PHP, and nodejs. Provides the sub-transaction barrier function to elegantly solve problems such as idempotence, suspension, and null compensation.

After reading this article, welcome to visit https://github.com/dtm-labs/dtm project, give a star to support!


叶东富
1.1k 声望6.1k 粉丝