What is TCC mode

The TCC mode is one of tX-LCN distributed transaction modes. T-try: attempts to execute the service, C-confirm: confirms the service, and C-cancel: cances the service

The principle of

Compared with traditional transaction mechanism (X/Open XA two-phase-commit), TCC transaction mechanism is characterized in that it does not rely on resource manager (RM) support for XA, but implements distributed transactions by scheduling business logic (provided by business systems). There are three operations: Try: Try the service, Confirm: Confirm the service, and Cancel: Cancel the service.

Model features

  • This pattern is highly embedded in the code, requiring each business to write three steps of operations.
  • This mode supports a wide range of applications with and without local transaction control.
  • Data consistency control is almost completely controlled by developers, which has high requirements for business development.

The source code interpretation

Let’s start by looking at several key classes: TransactionAspect class, DTXLogicWeaver Distributed transaction scheduler, and DTXServiceExecutor Distributed transaction executor

TransactionAspect role

  • The source code
@Aspect @Component public class TransactionAspect implements Ordered { private static final Logger log = LoggerFactory.getLogger(TransactionAspect.class); private final TxClientConfig txClientConfig; private final DTXLogicWeaver dtxLogicWeaver; public TransactionAspect(TxClientConfig txClientConfig, DTXLogicWeaver dtxLogicWeaver) { this.txClientConfig = txClientConfig; this.dtxLogicWeaver = dtxLogicWeaver; } @Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.TccTransaction)") public void tccTransactionPointcut() { } @Around("tccTransactionPointcut() && ! lcnTransactionPointcut()&& ! txcTransactionPointcut() && ! txTransactionPointcut()") public Object runWithTccTransaction(ProceedingJoinPoint point) throws Throwable { DTXInfo dtxInfo = DTXInfo.getFromCache(point); TccTransaction tccTransaction = (TccTransaction)dtxInfo.getBusinessMethod().getAnnotation(TccTransaction.class); dtxInfo.setTransactionType("tcc"); dtxInfo.setTransactionPropagation(tccTransaction.propagation()); DTXLogicWeaver var10000 = this.dtxLogicWeaver; point.getClass(); return var10000.runTransaction(dtxInfo, point::proceed); } public int getOrder() { return this.txClientConfig.getDtxAspectOrder(); }Copy the code

From the source code of this class, we can see how to do this by parsing the @tCCTransaction annotation. The code calls the DTXLogicWeaver class

DTXLogicWeaver role

public Object runTransaction(DTXInfo dtxInfo, BusinessCallback business) throws Throwable { if (Objects.isNull(DTXLocalContext.cur())) { DTXLocalContext.getOrNew(); log.debug("<---- TxLcn start ---->"); DTXLocalContext dtxLocalContext = DTXLocalContext.getOrNew(); TxContext txContext; if (this.globalContext.hasTxContext()) { txContext = this.globalContext.txContext(); dtxLocalContext.setInGroup(true); log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId()); } else { txContext = this.globalContext.startTx(); } if (Objects.nonNull(dtxLocalContext.getGroupId())) { dtxLocalContext.setDestroy(false); } dtxLocalContext.setUnitId(dtxInfo.getUnitId()); dtxLocalContext.setGroupId(txContext.getGroupId()); dtxLocalContext.setTransactionType(dtxInfo.getTransactionType()); TxTransactionInfo info = new TxTransactionInfo(); info.setBusinessCallback(business); info.setGroupId(txContext.getGroupId()); info.setUnitId(dtxInfo.getUnitId()); info.setPointMethod(dtxInfo.getBusinessMethod()); info.setPropagation(dtxInfo.getTransactionPropagation()); info.setTransactionInfo(dtxInfo.getTransactionInfo()); info.setTransactionType(dtxInfo.getTransactionType()); info.setTransactionStart(txContext.isDtxStart()); boolean var15 = false; Object var6; try { var15 = true; var6 = this.transactionServiceExecutor.transactionRunning(info); var15 = false; } finally { if (var15) { if (dtxLocalContext.isDestroy()) { synchronized(txContext.getLock()) { txContext.getLock().notifyAll(); } if (! dtxLocalContext.isInGroup()) { this.globalContext.destroyTx(); } DTXLocalContext.makeNeverAppeared(); TracingContext.tracing().destroy(); } log.debug("<---- TxLcn end ---->"); } } if (dtxLocalContext.isDestroy()) { synchronized(txContext.getLock()) { txContext.getLock().notifyAll(); } if (! dtxLocalContext.isInGroup()) { this.globalContext.destroyTx(); } DTXLocalContext.makeNeverAppeared(); TracingContext.tracing().destroy(); } log.debug("<---- TxLcn end ---->"); return var6; } else { return business.call(); }}Copy the code

DTXServiceExecutor (DTXServiceExecutor, DTXServiceExecutor, DTXServiceExecutor

DTXServiceExecutor role

/** * Transaction service execution ** @param info info * @return Object * @throws Throwable Throwable */ public Object transactionRunning(TxTransactionInfo info) throws Throwable { // 1. String transactionType = info.getTransactionType(); / / 2. The acquisition transaction propagation state DTXPropagationState propagationState = propagationResolver. ResolvePropagationState (info); / / 2.1 if not participate in a distributed transaction is terminated if (propagationState. IsIgnored () {return info. GetBusinessCallback () call (); } / / 3. Get local distributed transaction controller DTXLocalControl DTXLocalControl = txLcnBeanHelper. LoadDTXLocalControl (transactionType, propagationState); 4.1 Record the transaction type to the transaction context Set<String> transactionTypeSet = globalContext.txContext(info.getGroupId()).getTransactionTypes(); transactionTypeSet.add(transactionType); dtxLocalControl.preBusinessCode(info); Txlogger.txtrace (info.getgroupid (), info.getUnitid (), "pre Business code, unit Type: {}", transactionType); / / 4.3 perform business Object result. = dtxLocalControl doBusinessCode (info); Txlogger.txtrace (info.getgroupid (), info.getunitid (), "Business Success "); dtxLocalControl.onBusinessCodeSuccess(info, result); return result; } catch (TransactionException e) { txLogger.error(info.getGroupId(), info.getUnitId(), "before business code error"); throw e; } catch (Throwable e) {// 4.5 Service execution fails TXLogger. error(info.getgroupid (), info.getunitid (), Transactions. "business code error"); dtxLocalControl.onBusinessCodeError(info, e); throw e; DtxLocalControl} finally {/ / 4.6 business has been completed. The postBusinessCode (info); }}Copy the code

As you can see from the above code, this class is the key class for the entire transaction execution.

The above is the core code of TCC mode, the other branch code will not be described. You can see that there is some code duplication with the LCN mode, the whole thing is to parse different annotations to follow different transaction modes

In actual combat

According to tX-lCN of the first distributed transaction, we planned two TCS, lCN-Order service and LCN-pay service respectively. Our idea is that order service calls payment service, and inserts insert data into order service table T_ORDER and payment service table t_pay respectively.

Order service core code and data sheet scripts

  • code
/** * @author:triumphxx * @date :2021/10/24 * @time :2:13 PM * http://blog.triumphxx.com.cn * @GitHub https://github.com/triumphxx * @Desc: **/ @RestController public class TccOrderController { @Autowired TOrderDao tOrderDao; @Autowired private RestTemplate restTemplate; Private static Map<String,Integer> maps = new HashMap<>(); private static Map<String,Integer> maps = new HashMap<>(); @PostMapping("/add-order-tcc") @Transactional(rollbackFor = Exception.class) @TccTransaction public String add(){ TOrder  bean = new TOrder(); Long no = Math.round((Math.random()+1) * 1000); bean.setTId(no.intValue()); bean.setTName("order"+no.intValue()); JSONObject date = new JSONObject(); date.put("tId",bean.getTId()); date.put("tName",bean.getTName()+"pay"); restTemplate.postForEntity("http://lcn-pay/add-pay-tcc",date,String.class); // int i = 1/0; tOrderDao.insert(bean); maps.put("a",no.intValue()); System.out.println(" this new order number is "+ no.intValue()); Return "Order added successfully "; } public String confirmAdd(){ System.out.println("order confirm "); System.out.println(" +maps.get("a")); maps.clear(); Return "Order added successfully "; } /** * public String cancelAdd(){Integer a = maps.get("a"); System.out.println("order cancel "); System.out.println(" delete order number :"+a); tOrderDao.deleteByPrimaryKey(a); maps.clear(); Return "Order added successfully "; }}Copy the code

Code interpretation: To use TCC mode, you only need to annotate @tCCTransaction to your method. Compared to LCN mode, we can see that there is a lot more code, so TCC mode depends on the developer to control the data consistency. The name of the method must start with confirm and cancel. In the same way, confirm is used to perform operations after a service is successfully executed. Cancel is the reverse of a try operation. Notice that there is a map data result in the code, which is the unique identifier of the try, confirm, and Cancel operations.

  • The script
CREATE TABLE `t_order` (
   `t_id` int(11) NOT NULL,
   `t_name` varchar(45) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
Copy the code

Payment service core code and data sheet scripts

  • code
/** * @author:triumphxx * @date :2021/10/24 * @time :2:26 PM * http://blog.triumphxx.com.cn * @GitHub https://github.com/triumphxx * @Desc: **/ @RestController public class TccPayController { @Autowired TPayDao tPayDao; Private static Map<String,Integer> maps = new HashMap<>(); private static Map<String,Integer> maps = new HashMap<>(); @PostMapping("/add-pay-tcc") @Transactional(rollbackFor = Exception.class) @TccTransaction public String addPay(@RequestBody Map map){ TPay tPay = new TPay(); tPay.setTId((Integer) map.get("tId")); tPay.setTName((String) map.get("tName")); tPayDao.insert(tPay); maps.put("a",(Integer) map.get("tId")); // int i = 1/0; System.out.println(" + (Integer) map.get("tId")); Return "New payment succeeded "; } public String confirmAddPay(Map map){ System.out.println("pay confirm"); System.out.println(" + maps.get("a")); maps.clear(); Return "New payment succeeded "; } /** * public String cancelAddPay(map map){Integer a = maps.get("a"); System.out.println("pay cancel"); System.out.println(" + maps.get("a")); tPayDao.deleteByPrimaryKey(a); maps.clear(); Return "Cancel payment successfully "; }}Copy the code
  • The script
CREATE TABLE `t_pay` (
     `t_id` int(11) NOT NULL,
     `t_name` varchar(45) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
Copy the code

The test process

  • Start theRedis
  • Start theTM
  • Start the registryeureka-server
  • Start the servicelcn-order
  • Start the servicelcn-pay
  • Request interfacehttp://localhost:8001/add-order
  • The code raises an exception to see if the data is rolled back

summary

This article we analyzed tX-LCN distributed transaction TCC mode principle and related source code, as well as build services for testing. I hope I can help you. Source code address source code portal