What is TCC? TCC is the abbreviation of Try, Confirm and Cancel. It was first proposed by a paper entitled Life beyond Distributed Transactions:an Apostate's Opinion published by Pat Helland in 2007.
TCC composition
TCC is divided into three stages
- Try phase: try to execute, complete all business checks (consistency), and reserve necessary business resources (quasi isolation)
- Confirm phase: if the Try of all branches is successful, go to the confirm phase. Confirm really executes the business without any business check, and only uses the business resources reserved in the Try phase
- Cancel phase: if one Try of all branches fails, go to the cancel phase. Cancel releases the business resources reserved in the Try phase.
There are three roles in TCC distributed transactions, which are the same as the classic XA distributed transactions:
- The AP / application initiates a global transaction and defines which transaction branches are included in the global transaction
- RM / resource manager is responsible for the management of various resources in branch transactions
- TM / transaction manager is responsible for coordinating the correct execution of global transactions, including the execution of Confirm and Cancel, and handling network exceptions
If we want to conduct a business similar to inter-bank transfer, the transfer out and transfer in are in different micro services respectively. The typical sequence diagram of a successfully completed TCC transaction is as follows:
TCC practice
Let's develop a TCC transaction
The distributed transaction framework used in our example is dtm, which supports distributed transactions very gracefully. Let's explain the composition of TCC in detail
Let's write a specific Try/Confirm/Cancel processing function
@RequestMapping("TransOutTry") public Map<String, String> TransOutTry() { logger.info("TransOutTry"); Map<String, String> result = new HashMap<>(); result.put("dtm_result", "SUCCESS"); return result; } @RequestMapping("TransOutConfirm") public Map<String, String> TransOutConfirm(HttpServerResponse response) { logger.info("TransOutConfirm"); Map<String, String> result = new HashMap<>(); result.put("dtm_result", "SUCCESS"); return result; } @RequestMapping("TransOutCancel") public Map<String, String> TransOutCancel() { logger.info("TransOutCancel"); Map<String, String> result = new HashMap<>(); result.put("dtm_result", "SUCCESS"); return result; } @RequestMapping("TransInTry") public Map<String, String> TransInTry() { logger.info("TransInTry"); Map<String, String> result = new HashMap<>(); result.put("dtm_result", "SUCCESS"); return result; } @RequestMapping("TransInConfirm") public Map<String, String> TransInConfirm() { logger.info("TransInConfirm"); Map<String, String> result = new HashMap<>(); result.put("dtm_result", "SUCCESS"); return result; } @RequestMapping("TransInCancel") public Map<String, String> TransInCancel() { logger.info("TransInCancel"); Map<String, String> result = new HashMap<>(); result.put("dtm_result", "SUCCESS"); return result; }
At this point, the processing functions of each sub transaction are OK, and then start the TCC transaction for branch calls
@RequestMapping("fireTcc") public String fireTcc() { Function<Tcc, Boolean> function = TccController::tccTrans; return tcc.tccGlobalTransaction(function); } public static Boolean tccTrans(Tcc tcc) { try { boolean a = tcc.callBranch("", svc + "/TransOutTry", svc + "/TransOutConfirm", svc + "/TransOutCancel"); boolean b = tcc.callBranch("", svc + "/TransInTry", svc + "/TransInConfirm", svc + "/TransInCancel"); return a && b; } catch (Exception e) { e.printStackTrace(); } return false; }
So far, a complete TCC distributed transaction is written.
If you want to run a successful example completely, refer to the example yedf / dtmcli Java sample, which is very simple to run
# Deploy start dtm # docker version 18 or above is required git clone https://github.com/yedf/dtm cd dtm docker-compose up # Start another command line git clone https://github.com/yedf/dtmcli-java-sample.git cd dtmcli-java-sample # Compile and run the example main/src/main/java/com/github/viticis/dtmclijavaexamples/DtmcliJavaSampleApplication
Rollback of TCC
What if the bank finds that the account of user 2 is abnormal and fails to return when it is ready to transfer the amount to user 2? We can simulate this by having TransIn return a failure
@RequestMapping("TransInTry") public Map<String, String> TransInTry() { logger.info("TransInTry"); Map<String, String> result = new HashMap<>(); result.put("dtm_result", "FAILURE"); return result; }
We give the sequence diagram of transaction failure interaction
The difference between this and a successful TCC is that when a sub transaction returns a failure, the global transaction is subsequently rolled back and the Cancel operation of each sub transaction is called to ensure that all global transactions are rolled back.
In the TCC transaction mode, many readers will ask, what happens if Confirm/Cancel fails? This is a good question, which means that you are thinking deeply about the TCC transaction mode. The first case is temporary failure, such as network failure, application or database downtime. Retry such errors and finally return success; The other case is business failure. According to the TCC agreement, resources are locked in the first stage to ensure that sufficient resources can be used for Confirm/Cancel execution. In other words, in program logic, Confirm/Cancel is not allowed to return business failure. If a business failure occurs, it is a bug and needs to be manually repaired by the developer.
In this article, we introduce the theoretical knowledge of TCC, and give a complete process of writing a TCC transaction through an example, including normal successful completion and successful rollback. I believe readers have a deep understanding of TCC through this article.
More comprehensive knowledge of distributed transactions
The examples used in this article are excerpted from yedf/dtm. It supports a variety of transaction modes: TCC, SAGA, XA and cross language support for transaction messages. It has supported clients in golang, python, Java, PHP, nodejs and other languages. Refer to the SDK s of various languages. It provides sub transaction barrier function to gracefully solve idempotent, suspension, null compensation and other problems.