Principle of TCC
In order to solve the problem of large granularity resource locking during transaction operation, a new transaction model is proposed based on the definition of transaction at business level. The lock granularity is completely controlled by the business itself. It is essentially an idea of compensation. It divides the transaction into two phases, Try, Confirm, and Cancel. The logic at each stage is controlled by the business code. This gives complete control over the lock granularity of the transaction. Businesses can achieve higher performance at the expense of isolation.
- Try stage:
- Try: Attempts to execute services
- Complete all business checks (consistency)
- Reserve necessary business resources (quasi-isolation)
- Try: Attempts to execute services
- Confirm/Cancel phase:
- Confirm: Indicates that the service is executed
- Actually doing business
- Don’t do any business checks
- The confirm operation is idempotent
- Cancel: Cancels the service
- Release service resources reserved during the Try phase
- The Cancel operation is idempotent
- Confirm and Cancel are mutually exclusive
- Confirm: Indicates that the service is executed
The overall process is shown as follows:
From: www.iocoder.cn/TCC-Transac…
Tcc ws-transaction principle
In TCC-Transaction, a TCC transaction can contain multiple business activities. Tcc-transaction abstracts each transaction activity into a transaction participant, and each transaction can contain multiple participants.
Let’s take a look at the TCC-Transaction infrastructure design.
Obviously, the TCC-Transaction entry is the transaction interceptor, which organizes all the components together in two facets.
Read the source code prereference
The transaction
Let’s first look at the properties of the Transaction class.
/* * private TransactionXid xid; /** * private TransactionStatus status; /** * TransactionType */ private TransactionType TransactionType; /** * Number of retries */ private volatile int retriedCount = 0; /** * createTime */ private Date createTime = new Date(); / private Date lastUpdateTime = new Date(); / private Date lastUpdateTime = new Date(); /** * private long version = 1; / / private List<Participant> participants = new ArrayList<Participant>(); Private Map<String, Object> attachments = new ConcurrentHashMap<String, Object>();Copy the code
This includes TransactionXid, TransactionStatus, TransactionType, and Participant.
TransactionXid
/** * private int formatId = 1; /** * globalTransactionId */ private byte[] globalTransactionId; /** * Branch transaction number */ private byte[] branchQualifier;Copy the code
TransactionStatus
TRYING(1), CONFIRMING(2), CANCELLING(3);
Copy the code
TransactionType
/** * ROOT transaction/ROOT(1), /** * BRANCH transaction/BRANCH(2);Copy the code
participants
/** * transaction id */ private TransactionXid xid; /** * Confirm method context */ private InvocationContext confirmInvocationContext; /** * Cancel method context */ private InvocationContext cancelInvocationContext; /** ** private Terminator Terminator = new Terminator(); /** * context editing */ Class<? extends TransactionContextEditor> transactionContextEditorClass; public voidrollback() {
terminator.invoke(new TransactionContext(xid, TransactionStatus.CANCELLING.getId()), cancelInvocationContext, transactionContextEditorClass);
}
public void commit() {
terminator.invoke(new TransactionContext(xid, TransactionStatus.CONFIRMING.getId()), confirmInvocationContext, transactionContextEditorClass);
}
Copy the code
Transaction manager
Provides methods for acquiring, initiating, committing, rolling back transactions, adding participants, and so on.
Have a look at these first, have an impression, below will explain the content in detail.
Transaction interceptor
The transaction interceptor is implemented by two Spring AOP facets, as shown below.
Let’s take a look at a usage example:
Participants need to declare methods of the three types try/confirm/Cancel, which correspond to TCC operations. In the program, tag the try method with @compensable and fill in the corresponding Confirm/Cancel method.
// try @compensable (confirmMethod ="confirmRecord", cancelMethod = "cancelRecord". transactionContextEditor = DubboTransactionContextEditor.class) public String record(CapitalTradeOrderDto tradeOrderDto) Public void confirmRecord(CapitalTradeOrderDto tradeOrderDto) {} // Cancel public void cancelRecord(CapitalTradeOrderDto tradeOrderDto) { }Copy the code
There is clearly a way to make @Compensable annotations, so let’s look for it first. In the spring support of the source code, there is a configuration like this:
<bean id="transactionConfigurator" class="Org. Mengyun. Tcctransaction. Spring. Support. :"
init-method="init"/>
<bean id="compensableTransactionAspect" class="org.mengyun.tcctransaction.spring.ConfigurableTransactionAspect"
init-method="init">
<property name="transactionConfigurator" ref="transactionConfigurator"/>
</bean>
<bean id="resourceCoordinatorAspect" class="org.mengyun.tcctransaction.spring.ConfigurableCoordinatorAspect"
init-method="init">
<property name="transactionConfigurator" ref="transactionConfigurator"/>
</bean>
Copy the code
SpringTransactionConfigurator is a configuration related classes, we first no matter.
ConfigurableTransactionAspect
First look at the class diagram:
Let’s look at ConfigurableTransactionAspect class.
@Aspect
public class ConfigurableTransactionAspect extends CompensableTransactionAspect implements Ordered {
private TransactionConfigurator transactionConfigurator;
public void init() {/ / set the transaction manager TransactionManager TransactionManager. = transactionConfigurator getTransactionManager (); / / this is concrete block implementation class CompensableTransactionInterceptor CompensableTransactionInterceptor = new CompensableTransactionInterceptor(); compensableTransactionInterceptor.setTransactionManager(transactionManager); compensableTransactionInterceptor.setDelayCancelExceptions(transactionConfigurator.getRecoverConfig().getDelayCancelExce ptions()); this.setCompensableTransactionInterceptor(compensableTransactionInterceptor); } @Override public intgetOrder() {// The smaller the order, the higher the priority.return Ordered.HIGHEST_PRECEDENCE;
}
public void setTransactionConfigurator(TransactionConfigurator transactionConfigurator) { this.transactionConfigurator = transactionConfigurator; }}Copy the code
ConfigurableTransactionAspect inherited CompensableTransactionAspect realized Orderd at the same time, and for the minimum Order value, this is the first section we are looking for.
In this case, the subclass provides initialization for the parent class, and the specific logic is in the parent class. Let’s look at the logic of the parent class.
@Aspect
public abstract class CompensableTransactionAspect {
private CompensableTransactionInterceptor compensableTransactionInterceptor;
public void setCompensableTransactionInterceptor(CompensableTransactionInterceptor compensableTransactionInterceptor) { this.compensableTransactionInterceptor = compensableTransactionInterceptor; } /** * Pointcut */ @pointcut ("@annotation(org.mengyun.tcctransaction.api.Compensable)")
public void compensableService() {} /** * section * @param PJP * @return
* @throws Throwable
*/
@Around("compensableService()")
public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable {
return compensableTransactionInterceptor.interceptCompensableMethod(pjp);
}
public abstract int getOrder();
}
Copy the code
Can see the specific logic or in compensableTransactionInterceptor, we continue to follow.
Public Object interceptCompensableMethod (ProceedingJoinPoint PJP) throws Throwable {/ / to get specific annotated method of propagation behavior and transaction context CompensableMethodContext compensableMethodContext = new CompensableMethodContext(pjp); // transactionManager is singleton, which contains a threadLocal, Inside was a Deque (Deque) / / whether the Deque in the threadlocal is empty Boolean isTransactionActive = transactionManager. IsTransactionActive (); // If the transaction context is valid // the propagation level is to support the current transaction but not the new transaction, and there is no transaction in the current thread and the transaction context is empty // The exception is illegal thrownif(! TransactionUtils.isLegalTransactionContext(isTransactionActive, compensableMethodContext)) { throw new SystemException("no active compensable transaction while propagation is mandatory for method "+ compensableMethodContext.getMethod().getName()); } // If the propagation level is mandatory, or (new transactions are allowed and there are no transactions in the thread, And the transaction context is empty) is the root transaction / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- // If the above conditions are not met and (the propagation level of the transaction is to support the current transaction or to support the current transaction, the transaction is not allowed to start) and there are no active transactions, And transaction context is not null / / branch transaction is the switch (compensableMethodContext. GetMethodRole (isTransactionActive)) {case ROOT:
return rootMethodProceed(compensableMethodContext);
case PROVIDER:
returnproviderMethodProceed(compensableMethodContext); Default: // If the transaction exists in a thread or the transaction propagation behavior is to support the current transaction, the non-transaction execution is directly executedreturnpjp.proceed(); }}Copy the code
First get the parameters for the annotation.
public CompensableMethodContext(ProceedingJoinPoint pjp) { this.pjp = pjp; This.method = getCompensableMethod(); This.com pensable = method.getannotation (Compensable. Class); Propagation = compensable. Propagation (); // get the transactionContext this.transactioncontext = FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs()); }Copy the code
Tcc-transaction supports four propagation behaviors.
/** * support the current transaction, if there is no transaction, create a new transaction. */ REQUIRED(0), /** * supports the current transaction, and if no transaction is currently available, it is executed nontransactionally. */ SUPPORTS(1), /** * SUPPORTS the current transaction and throws an exception if there is no transaction currently. */ MANDATORY(2), /** * Creates a transaction, and suspits the current transaction if one exists. */ REQUIRES_NEW(3);Copy the code
FactoryBuilder singleton factory is an abstract, the whole sentence is from the annotation transactionContextEditor. Gain a singleton class transactionContextEditor, The transactionContextEditor’s GET method is then called to get the transaction context.
We went back to interceptCompensableMethod. IsTransactionActive Determines whether the current thread contains a transaction.
public boolean isTransactionActiveDeque<Transaction> transactions = current.get ();returntransactions ! = null && ! transactions.isEmpty(); }Copy the code
See if the current thread has a value in the two-way queue.
Keep going down.
If the propagation level is set to support the current transaction and no new transaction is supported, and there is no transaction in the current thread and the transaction context is empty, an exception is illegally thrownif(! TransactionUtils.isLegalTransactionContext(isTransactionActive, compensableMethodContext)) { throw new SystemException("no active compensable transaction while propagation is mandatory for method " + compensableMethodContext.getMethod().getName());
}
Copy the code
/ / necessary for illegal public static Boolean isLegalTransactionContext (Boolean isTransactionActive, CompensableMethodContext compensableMethodContext) {if(compensableMethodContext. GetPropagation (.) the equals (Propagation) MANDATORY) / / transaction Propagation behavior in order to support the current transaction, there is no transaction, throw an exception &&! IsTransactionActive / / not the current transaction && compensableMethodContext. GetTransactionContext () = = null / / transaction context is empty) {return false;
}
return true;
}
Copy the code
Go on.
Switch (compensableMethodContext getMethodRole (isTransactionActive)) {/ / it is the branch root transaction or transactions / / if spread level for must open transaction or new (New transactions are allowed, there are no transactions in the thread, and the transaction context is empty) is the root transactioncase ROOT:
returnrootMethodProceed(compensableMethodContext); // If the above conditions are not met and (the propagation level of the transaction is to support the current transaction or to support the current transaction, the transaction is not allowed to start), there are no active transactions and the transaction context is not emptycase PROVIDER:
returnproviderMethodProceed(compensableMethodContext); Default: // If the transaction exists in a thread or the transaction propagation behavior is to support the current transaction, the non-transaction execution is directly executedreturnpjp.proceed(); }}Copy the code
Take a look at how root transactions are handled.
private Object rootMethodProceed(CompensableMethodContext compensableMethodContext) throws Throwable {
Object returnValue = null; Transaction transaction = null; / / confirm whether synchronization Boolean asyncConfirm = compensableMethodContext. The getAnnotation () asyncConfirm (); / / whether the synchronization cancel Boolean asyncCancel = compensableMethodContext. GetAnnotation () asyncCancel (); // Add delay cancel related exception Set<Class<? extends Exception>> allDelayCancelExceptions = new HashSet<Class<? extends Exception>>(); allDelayCancelExceptions.addAll(this.delayCancelExceptions); allDelayCancelExceptions.addAll(Arrays.asList(compensableMethodContext.getAnnotation().delayCancelExceptions())); Try {// create root transaction // create a transaction in memory, Declare the transaction as root // persist the transaction to Redis, ZooKeeper, mysql, or disk // register the transaction in the treadLocal deque of transactionManager transactionManager.begin(compensableMethodContext.getUniqueIdentity()); Try {// executereturnValue = compensableMethodContext.proceed(); } catch (Throwable tryingException) {// Exception handling // If not the exception type that needs to be delayedif(! isDelayCancelException(tryingException, allDelayCancelExceptions)) { logger.warn(String.format("compensable transaction trying failed. transaction content:%s", JSON.toJSONString(transaction)), tryingException); // Get the deque header of the current thread, / / change the state of affairs And persistent change of state of affairs / / / / delete persistent transaction rollback all participants transactionManager. Rollback (asyncCancel); } // throw tryingException; } / / commit / / and the rollback steps transactionManager.com MIT (asyncConfirm); } the finally {/ / remove the transaction from the current thread transaction queue transactionManager. CleanAfterCompletion (transaction); }return returnValue;
}
Copy the code
Take a look at the BEGIN method.
/** * initiate root transaction * @param uniqueIdentify * @returnTransaction */ public Transaction BEGIN (Object uniqueIdentify) {// Create root Transaction Transaction = new Transaction(uniqueIdentify,TransactionType.ROOT); / / store transaction transactionRepository. Create (transaction); // registerTransaction registerTransaction(transaction);return transaction;
}
Copy the code
private void registerTransaction(Transaction transaction) {
if (CURRENT.get() == null) {
CURRENT.set(new LinkedList<Transaction>());
}
CURRENT.get().push(transaction);
}
Copy the code
Branch transaction processing method.
private Object providerMethodProceed(CompensableMethodContext compensableMethodContext) throws Throwable { Transaction transaction = null; boolean asyncConfirm = compensableMethodContext.getAnnotation().asyncConfirm(); boolean asyncCancel = compensableMethodContext.getAnnotation().asyncCancel(); Try {/ / transaction and ask the state of the switch (TransactionStatus. The valueOf (compensableMethodContext. GetTransactionContext (). The getStatus ())) {/ / If I'm in the try phasecaseTRYING: // Create a transaction in memory, declare it as a branch transaction, Transaction ID is the transaction ID in the transaction context // Persistent transaction // registered in deque transaction = transactionManager.propagationNewBegin(compensableMethodContext.getTransactionContext());return compensableMethodContext.proceed();
caseCONFIRMING: Try {// propagate the fetch branch // fetch the transaction from the persistent // register transaction in the deque = transactionManager.propagationExistBegin(compensableMethodContext.getTransactionContext()); transactionManager.commit(asyncConfirm); } catch (NoExistedTransactionException excepton) { //the transaction has been commit,ignore it. }break;
caseCANCELLING: Try {// propagate the fetch branch transaction = transactionManager.propagationExistBegin(compensableMethodContext.getTransactionContext()); transactionManager.rollback(asyncCancel); } catch (NoExistedTransactionException exception) { //the transaction has been rollback,ignore it. }break;
}
} finally {
transactionManager.cleanAfterCompletion(transaction);
}
Method method = compensableMethodContext.getMethod();
return ReflectionUtils.getNullValue(method.getReturnType());
}
Copy the code
Look at the propagationNewBegin method.
/** * propagate initiated branch transaction * @param transactionContext transactionContext * @return*/ Public Transaction propagationNewBegin(TransactionContext TransactionContext) {// Create a branch Transaction Transaction transaction = new Transaction(transactionContext); / / store transaction transactionRepository. Create (transaction); // registerTransaction registerTransaction(transaction);return transaction;
}
Copy the code
conclusion
Let’s get the logic straight.
- 1) The section is cut in. Some parameters of the section are obtained first.
- methods
- annotations
- Propagation behavior
- And transaction context
- 2) Determine whether the transaction is active (determine whether the Deque in the current thread is empty).
- 3) To judge whether the transaction is legal or not, the following three conditions must be met if the transaction is illegal:
- The transaction propagation behavior is MANDATORY and the current transaction is supported. If there is no transaction, an exception is thrown.
- There are currently no active transactions
- The transaction context is empty.
- 4) Determine whether a new Transaction (root Transaction or branch Transaction) or whether it is already in a Transaction, and execute it directly (without creating Transaction).
- There are two cases of root transactions:
- Transaction propagation behavior is REQUIRED (current transaction is supported, new transaction is started if no transaction exists), there are currently no active transactions, and the transaction context is empty.
- The transaction propagation behavior is REQUIRES_NEW.
- Case of branch transactions:
- The transaction propagation behavior is REQUIRED or MANDATORY, and there are currently no active transactions and the transaction context is not empty.
- No Transaction is created:
- The current thread has an active transaction, or the propagation behavior is SUPPORTS (SUPPORTS the current transaction, non-transaction execution if there are no transactions).
- There are two cases of root transactions:
- 5) How to handle a root transaction
- 5.1) Get whether confirm and Cancel status are synchronized in the annotation.
- 5.2) Get exceptions related to delayed Cancel.
- 5.3) Create the root transaction
- 5.3.1) Create a root transaction that randomly generates the transaction ID, the transaction state is trying, and the transaction type is root.
- 5.3.2) Store the root transaction created in the previous step to the transaction storage (mysql, Redis, ZooKeeper, File).
- 5.3.3) Register transactions into the Deque stack tail of TransactionManager’s TreadLocal.
- 5.4) Perform specific business (and move on to the next interceptor).
- 5.5) If an exception occurs in the previous step, determine whether it is an exception that needs to be delayed. If yes, continue to throw the exception upward. If not, call Rollback of transactionManager.
- 5.5.1) Retrieve the current transaction from the Deque.
- 5.5.2) Change the transaction state to CANCELLING.
- 5.5.3) Update the transaction state in the transaction store.
- 5.5.4) Call transaction rollback methods synchronously or asynchronously, i.e. loop through all participants and call their rollback methods.
- 5.5.5) Delete transactions from transactional storage.
- 5.6) If no exception occurs, commit, which is basically the same as the above rollback steps.
- 5.7) Clean up the scene and delete transaction from the deque. If the deque is empty, clean up ThreadLocal.
- 6) How to handle a branch transaction
- 6.1) Obtain whether confirm and Cancel status are synchronized in the annotation.
- 6.2) Determine the transaction phase.
- 6.2.1) TRYING stage
- 6.2.1.1) create a BRANCH transaction using the transaction ID in the transaction context, the transaction state is Trying, and the transaction type is BRANCH.
- 6.2.1.2) Store the branch transaction created in the previous step to the transaction storage (mysql, Redis, ZooKeeper, File).
- 6.2.1.3) register transactions into the Deque stack tail of TransactionManager’s TreadLocal.
- 6.2.1.4) Execute business logic directly without handling exceptions
- 6.2.2) CONFIRMING stage
- 6.2.2.1) Query the related transaction to the transaction memory according to the transaction ID in the transaction context (stored to the transaction memory in the try phase)
- 6.2.2.2) Change the transaction state
- 6.2.2.3) Sign up for Deque.
- 6.2.2.4) call transactionManager commit.
- 6.2.2.4.1) get transaction from deque
- 6.2.2.4.2) Change the transaction state
- 6.2.2.4.3) changes the transaction state in transaction storage.
- 6.2.2.4.4) call the commit method for all participants.
- 6.2.2.4.5) deletes transactions in transactional storage.
- 6.2.3) The CANCELLING stage and CONFIRMING stage are similar. 6.3) Clean up the site. 6.4) Returns a null value if the TRYING or CONFIRMING stage is used.
- 6.2.1) TRYING stage
- 7) No new transaction processing method, directly execute.
Understand the difference between root and branch transactions.
- Rollback or commit after the root transaction is complete.
- A branch transaction is not required to rollback or commit during the TRYING phase. It is required to wait until the CONFIRMING phase or the CANCELLING phase.
Two questions:
- If the root transaction is rollback and commit, the related transaction is deleted. How do branch transactions perceive COMMIT or ROLLBACK?
- How can other branch transactions or root transactions be aware of the current transaction state?
ConfigurableCoordinatorAspect
Let’s take a look at the class diagram.
To see ConfigurableCoordinatorAspect class.
@Aspect
public class ConfigurableCoordinatorAspect extends ResourceCoordinatorAspect implements Ordered {
private TransactionConfigurator transactionConfigurator;
public void init() {
ResourceCoordinatorInterceptor resourceCoordinatorInterceptor = new ResourceCoordinatorInterceptor();
resourceCoordinatorInterceptor.setTransactionManager(transactionConfigurator.getTransactionManager());
this.setResourceCoordinatorInterceptor(resourceCoordinatorInterceptor);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
public void setTransactionConfigurator(TransactionConfigurator transactionConfigurator) { this.transactionConfigurator = transactionConfigurator; }}Copy the code
And the above ConfigurableTransactionAspect generally similar, but lower priority. When ConfigurableTransactionAspect execution to proceed, will enter this aspect.
Look at the parent class.
@Aspect public abstract class ResourceCoordinatorAspect { private ResourceCoordinatorInterceptor resourceCoordinatorInterceptor; /** * Pointcut(@pointcut)"@annotation(org.mengyun.tcctransaction.api.Compensable)")
public void transactionContextCall() {} /** * wrap around * @param PJP * @return
* @throws Throwable
*/
@Around("transactionContextCall()")
public Object interceptTransactionContextMethod(ProceedingJoinPoint pjp) throws Throwable {
return resourceCoordinatorInterceptor.interceptTransactionContextMethod(pjp);
}
public void setResourceCoordinatorInterceptor(ResourceCoordinatorInterceptor resourceCoordinatorInterceptor) {
this.resourceCoordinatorInterceptor = resourceCoordinatorInterceptor;
}
public abstract int getOrder();
}
Copy the code
See interceptTransactionContextMethod directly.
public Object interceptTransactionContextMethod(ProceedingJoinPoint pjp) throws Throwable {
Transaction transaction = transactionManager.getCurrentTransaction();
if(transaction ! = null) { switch (transaction.getStatus()) {case TRYING:
enlistParticipant(pjp);
break;
case CONFIRMING:
break;
case CANCELLING:
break; }}return pjp.proceed(pjp.getArgs());
}
Copy the code
- The first step is to retrieve the transaction from the previous interceptor from the thread Deque.
- Call enlistParticipant if it is a try state, otherwise nothing is done.
Take a look at what the enlistParticipant method does.
private void enlistParticipant(ProceedingJoinPoint pjp) throws IllegalAccessException, InstantiationException {/ / get the @ Compensable annotation Method Method. = CompensableMethodUtils getCompensableMethod (PJP);if (method == null) {
throw new RuntimeException(String.format("join point not found method, point is : %s", pjp.getSignature().getName())); } Compensable compensable = method.getAnnotation(Compensable.class); / / confirmed the execution of business method and cancel the execution of business method String confirmMethodName = compensable. ConfirmMethod (); String cancelMethodName = compensable.cancelMethod(); / / get the current thread affairs first element (stack) Transaction Transaction = transactionManager. GetCurrentTransaction (); TransactionXid xid = new TransactionXid(transaction.getxId ().getGlobalTransactionId())); // Set the transaction contextif(FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs()) == null) { FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().set(new TransactionContext(xid, TransactionStatus.TRYING.getId()), pjp.getTarget(), ((MethodSignature) pjp.getSignature()).getMethod(), pjp.getArgs()); } / / the Class Class targetClass = ReflectionUtils. GetDeclaringType (PJP. GetTarget (). The getClass (), method. The getName (), method.getParameterTypes()); InvocationContext confirmInvocation = new InvocationContext(targetClass, confirmMethodName, method.getParameterTypes(), pjp.getArgs()); InvocationContext cancelInvocation = new InvocationContext(targetClass, cancelMethodName, method.getParameterTypes(), pjp.getArgs()); Participant participant = new Participant( xid, confirmInvocation, cancelInvocation, compensable.transactionContextEditor()); / / a transaction participant is added to the transaction transactionManager. EnlistParticipant (the participant); }Copy the code
In a nutshell, create a set transaction context and generate an actor to add to the trasAction to modify the value in the transaction store.
conclusion
ConfigurableTransactionAspect as the first aspect, is responsible for the generation, handling, and storage of the transaction. ConfigurableCoordinatorAspect as the second section, responsible for the generation of participants and the creation of a transaction context.
Question answer
A branch transaction does not know whether it should commit or roll back.
2. Let’s look at the official demo.
package org.mengyun.tcctransaction.sample.dubbo.capital.api;
import org.mengyun.tcctransaction.api.Compensable;
import org.mengyun.tcctransaction.sample.dubbo.capital.api.dto.CapitalTradeOrderDto;
/**
* Created by changming.xie on 4/1/16.
*/
public interface CapitalTradeOrderService {
@Compensable
String record(CapitalTradeOrderDto tradeOrderDto);
}
Copy the code
This is a Dubbo RPC interface, which is annotated at @compensable, meaning that the remote RPC service is invoked once more. The remote participant is then added to the caller’s Transaction. That is, when the primary transaction is rolled back or committed, the remote participant’s interface is called and informed of the current transaction status. The remote participant calls different methods to do confirm or commit according to the status.
The following article:
/** * Transaction commit TCC transaction */ public voidcommit() {
for(Participant participant : participants) { participant.commit(); } /** * Participant commits a transaction */ public voidcommit() {
terminator.invoke(new TransactionContext(xid, TransactionStatus.CONFIRMING.getId()), confirmInvocationContext, transactionContextEditorClass);
}
Copy the code
When the remote branch receives the RPC call, it determines what phase the transaction is in and invokes the corresponding method.
switch (TransactionStatus.valueOf(transactionContext.getStatus())) {
caseTRYING: / / transmission branch launched the transaction transaction = transactionManager. PropagationNewBegin (transactionContext);return pjp.proceed();
caseCONFIRMING: try {/ / transmission for branch business transaction = transactionManager. PropagationExistBegin (transactionContext); / / to commit the transaction transactionManager.com MIT (); } catch (NoExistedTransactionException excepton) { //the transaction has been commit,ignore it. }break;
caseCANCELLING: try {/ / transmission for branch business transaction = transactionManager. PropagationExistBegin (transactionContext); / / rollback transaction transactionManager. The rollback (); } catch (NoExistedTransactionException exception) { //the transaction has been rollback,ignore it. }break;
}
Copy the code
At this point, all the normal procedures have been completed.
Todo transaction compensation
Todo dubbo support