接上一篇,我們已經分析了在整個消費的呼叫流程,現在只差發起真實的rpc遠端呼叫了,這篇文章,我們一起進入提供者的呼叫流程吧!
- 我們發起 accountService.payment(accountDTO); 的呼叫,在提供方,我們可以看到其實現類為AccountServiceImpl:
/**
* 扣款支付
*
* @param accountDTO 引數dto
* @return true
*/
@Override
@Tcc(confirmMethod = "confirm", cancelMethod = "cancel")
public boolean payment(AccountDTO accountDTO) {
final AccountDO accountDO = accountMapper.findByUserId(accountDTO.getUserId());
accountDO.setBalance(accountDO.getBalance().subtract(accountDTO.getAmount()));
accountDO.setFreezeAmount(accountDO.getFreezeAmount().add(accountDTO.getAmount()));
accountDO.setUpdateTime(new Date());
final int update = accountMapper.update(accountDO);
if (update != 1) {
throw new TccRuntimeException("資金不足!");
}
return Boolean.TRUE;
}
public boolean confirm(AccountDTO accountDTO) {
LOGGER.debug("============執行確認付款介面===============");
final AccountDO accountDO = accountMapper.findByUserId(accountDTO.getUserId());
accountDO.setFreezeAmount(accountDO.getFreezeAmount().subtract(accountDTO.getAmount()));
accountDO.setUpdateTime(new Date());
accountMapper.update(accountDO);
return Boolean.TRUE;
}
public boolean cancel(AccountDTO accountDTO) {
LOGGER.debug("============執行取消付款介面===============");
final AccountDO accountDO = accountMapper.findByUserId(accountDTO.getUserId());
accountDO.setBalance(accountDO.getBalance().add(accountDTO.getAmount()));
accountDO.setFreezeAmount(accountDO.getFreezeAmount().subtract(accountDTO.getAmount()));
accountDO.setUpdateTime(new Date());
accountMapper.update(accountDO);
return Boolean.TRUE;
}複製程式碼
我們發現它也有@Tcc註解,並且提供了confrim,cancel等真實的方法。通過前面一篇的分析,我們知道,他是springBean的一個實現類,同樣會走切面。
經過 TccTransactionFactoryServiceImpl 的 factoryOf方法,我們可以知道他會返回 ProviderTccTransactionHandler
@Override
public Class factoryOf(TccTransactionContext context) throws Throwable {
//如果事務還沒開啟或者 tcc事務上下文是空, 那麼應該進入發起呼叫
if (!tccTransactionManager.isBegin() && Objects.isNull(context)) {
return StartTccTransactionHandler.class;
} else if (tccTransactionManager.isBegin() && Objects.isNull(context)) {
return ConsumeTccTransactionIHandler.class;
} else if (Objects.nonNull(context)) {
return ProviderTccTransactionHandler.class;
}
return ConsumeTccTransactionIHandler.class;
}複製程式碼
- 最終我們來到 ProviderTccTransactionHandler.handler 方法:
/**
* 分散式事務提供者處理介面
* 根據tcc事務上下文的狀態來執行相對應的方法
*
* @param point point 切點
* @param context context
* @return Object
* @throws Throwable 異常
*/
@Override
public Object handler(ProceedingJoinPoint point, TccTransactionContext context) throws Throwable {
TccTransaction tccTransaction = null;
try {
switch (TccActionEnum.acquireByCode(context.getAction())) {
case TRYING:
try {
//建立事務資訊
tccTransaction = tccTransactionManager.providerBegin(context);
//發起方法呼叫
return point.proceed();
} catch (Throwable throwable) {
tccTransactionManager.removeTccTransaction(tccTransaction);
throw throwable;
}
case CONFIRMING:
//如果是confirm 通過之前儲存的事務資訊 進行反射呼叫
final TccTransaction acquire = tccTransactionManager.acquire(context);
tccTransactionManager.confirm();
break;
case CANCELING:
//如果是呼叫CANCELING 通過之前儲存的事務資訊 進行反射呼叫
tccTransactionManager.acquire(context);
tccTransactionManager.cancel();
break;
default:
break;
}
} finally {
tccTransactionManager.remove();
}
Method method = ((MethodSignature) (point.getSignature())).getMethod();
return getDefaultValue(method.getReturnType());
}複製程式碼
- TccTransactionContext 就是通過rpc json序列化後傳過來的物件,此時我們知道是在try階段,所以我們進入try
try {
//建立事務資訊
tccTransaction = tccTransactionManager.providerBegin(context);
//發起方法呼叫
return point.proceed();
} catch (Throwable throwable) {
tccTransactionManager.removeTccTransaction(tccTransaction);
throw throwable;
}複製程式碼
- 首先我們會建立提供者的事務資訊,並把他存起來,再把它存入threadlocal中,接著發起 point.proceed() 呼叫的時候,我們會進入
TccCoordinatorMethodAspect,由於是在try階段最終會進入:
/**
* 獲取呼叫介面的協調方法並封裝
*
* @param point 切點
*/
private void registerParticipant(ProceedingJoinPoint point, String transId) throws NoSuchMethodException {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Class<?> clazz = point.getTarget().getClass();
Object[] args = point.getArgs();
final Tcc tcc = method.getAnnotation(Tcc.class);
//獲取協調方法
String confirmMethodName = tcc.confirmMethod();
/* if (StringUtils.isBlank(confirmMethodName)) {
confirmMethodName = method.getName();
}*/
String cancelMethodName = tcc.cancelMethod();
/* if (StringUtils.isBlank(cancelMethodName)) {
cancelMethodName = method.getName();
}
*/
//設定模式
final TccPatternEnum pattern = tcc.pattern();
tccTransactionManager.getCurrentTransaction().setPattern(pattern.getCode());
TccInvocation confirmInvocation = null;
if (StringUtils.isNoneBlank(confirmMethodName)) {
confirmInvocation = new TccInvocation(clazz,
confirmMethodName, method.getParameterTypes(), args);
}
TccInvocation cancelInvocation = null;
if (StringUtils.isNoneBlank(cancelMethodName)) {
cancelInvocation = new TccInvocation(clazz,
cancelMethodName,
method.getParameterTypes(), args);
}
//封裝呼叫點
final Participant participant = new Participant(
transId,
confirmInvocation,
cancelInvocation);
tccTransactionManager.enlistParticipant(participant);
}複製程式碼
- 這裡獲取真實的confrim,cancel方法並存入當前的事務資訊中。然後發起真實的業務呼叫 ,即執行payment方法:
@Override
@Tcc(confirmMethod = "confirm", cancelMethod = "cancel")
public boolean payment(AccountDTO accountDTO) {
final AccountDO accountDO = accountMapper.findByUserId(accountDTO.getUserId());
accountDO.setBalance(accountDO.getBalance().subtract(accountDTO.getAmount()));
accountDO.setFreezeAmount(accountDO.getFreezeAmount().add(accountDTO.getAmount()));
accountDO.setUpdateTime(new Date());
final int update = accountMapper.update(accountDO);
if (update != 1) {
throw new TccRuntimeException("資金不足!");
}
return Boolean.TRUE;
}
`複製程式碼
- 當我們執行完該方法後,會返回,還記得我是在哪裡來執行這個方法的嗎?對,當然是切面,我們是在切面裡執行的,我們是在
PaymentServiceImpl.makePayment 切面裡面執行的! 請要理解這一點,執行完後,我們發起了 inventoryService.decrease(inventoryDTO) 呼叫
他的呼叫原理和上面一模一樣,只是在不同的模組裡面執行。當 makePayment 方法執行完後,我們該怎麼執行? 你還記得 StartTccTransactionHandler嗎,它可一直在那等呢。。
我們再來回顧下他的程式碼:
@Override
public Object handler(ProceedingJoinPoint point, TccTransactionContext context) throws Throwable {
Object returnValue;
try {
tccTransactionManager.begin();
try {
//發起呼叫 執行try方法
returnValue = point.proceed();
} catch (Throwable throwable) {
//異常執行cancel
tccTransactionManager.cancel();
throw throwable;
}
//try成功執行confirm confirm 失敗的話,那就只能走本地補償
tccTransactionManager.confirm();
} finally {
tccTransactionManager.remove();
}
return returnValue;
}複製程式碼
- 說到底,我們走了這麼久,其實到這裡,我們才執行完 returnValue = point.proceed(); 這一句程式碼。
沒有異常
- 我們會執行 tccTransactionManager.confirm(); 我們跟進去看程式碼:
/**
* 呼叫confirm方法 這裡主要如果是發起者呼叫 這裡呼叫遠端的還是原來的方法,不過上下文設定了呼叫confirm
* 那麼遠端的服務則會呼叫confirm方法。。
*/
void confirm() throws TccRuntimeException {
LogUtil.debug(LOGGER, () -> "開始執行tcc confirm 方法!start");
final TccTransaction currentTransaction = getCurrentTransaction();
if (Objects.isNull(currentTransaction)) {
return;
}
currentTransaction.setStatus(TccActionEnum.CONFIRMING.getCode());
coordinatorCommand.execute(new CoordinatorAction(CoordinatorActionEnum.UPDATE, currentTransaction));
final List<Participant> participants = currentTransaction.getParticipants();
List<Participant> participantList = Lists.newArrayListWithCapacity(participants.size());
boolean success = true;
Participant fail = null;
if (CollectionUtils.isNotEmpty(participants)) {
for (Participant participant : participants) {
try {
TccTransactionContext context = new TccTransactionContext();
context.setAction(TccActionEnum.CONFIRMING.getCode());
context.setTransId(participant.getTransId());
TransactionContextLocal.getInstance().set(context);
//通過反射呼叫rpc的confrim方法
executeParticipantMethod(participant.getConfirmTccInvocation());
participantList.add(participant);
} catch (Exception e) {
LogUtil.error(LOGGER, "執行confirm方法異常:{}", () -> e);
success = false;
fail = participant;
break;
}
}
}
executeHandler(success, currentTransaction, fail, participantList, participants);
}
private void executeHandler(boolean success, final TccTransaction currentTransaction, Participant fail,
List<Participant> participantList, final List<Participant> participants) {
if (success) {
TransactionContextLocal.getInstance().remove();
coordinatorCommand.execute(new CoordinatorAction(CoordinatorActionEnum.DELETE, currentTransaction));
} else {
//獲取還沒執行的,或者執行失敗的
final List<Participant> updateList =
participants.stream().skip(participantList.size()).collect(Collectors.toList());
currentTransaction.setParticipants(updateList);
coordinatorCommand.execute(new CoordinatorAction(CoordinatorActionEnum.UPDATE, currentTransaction));
assert fail != null;
throw new TccRuntimeException(fail.getConfirmTccInvocation().toString());
}
}
private void executeParticipantMethod(TccInvocation tccInvocation) throws Exception {
if (Objects.nonNull(tccInvocation)) {
final Class clazz = tccInvocation.getTargetClass();
final String method = tccInvocation.getMethodName();
final Object[] args = tccInvocation.getArgs();
final Class[] parameterTypes = tccInvocation.getParameterTypes();
final Object bean = SpringBeanUtils.getInstance().getBean(clazz);
MethodUtils.invokeMethod(bean, method, args, parameterTypes);
}
}複製程式碼
這段程式碼的邏輯,簡單理解起來,首先更新當前事務狀態(confrim),獲取當前事務的呼叫點的confrim方法,設定上下文,發起反射呼叫!
其實這裡通過除錯我們發現,發起confrim的方法為 AccountService.payment(AccountDTO accountDTO) ,不過設定的上下文狀態為confrim,
當我們發起反射呼叫的時候,我們會走到 ProviderTccTransactionHandler.handler 方法,這個方法或許你還有印象,我們再看一下它的程式碼:
@Override
public Object handler(ProceedingJoinPoint point, TccTransactionContext context) throws Throwable {
TccTransaction tccTransaction = null;
try {
switch (TccActionEnum.acquireByCode(context.getAction())) {
case TRYING:
try {
//建立事務資訊
tccTransaction = tccTransactionManager.providerBegin(context);
//發起方法呼叫
return point.proceed();
} catch (Throwable throwable) {
tccTransactionManager.removeTccTransaction(tccTransaction);
throw throwable;
}
case CONFIRMING:
//如果是confirm 通過之前儲存的事務資訊 進行反射呼叫
final TccTransaction acquire = tccTransactionManager.acquire(context);
tccTransactionManager.confirm();
break;
case CANCELING:
//如果是呼叫CANCELING 通過之前儲存的事務資訊 進行反射呼叫
tccTransactionManager.acquire(context);
tccTransactionManager.cancel();
break;
default:
break;
}
} finally {
tccTransactionManager.remove();
}
Method method = ((MethodSignature) (point.getSignature())).getMethod();
return getDefaultValue(method.getReturnType());
}複製程式碼
- 這裡因為上下文設定的狀態為:CONFIRMING ,所以會執行:
//如果是confirm 通過之前儲存的事務資訊 進行反射呼叫
final TccTransaction acquire = tccTransactionManager.acquire(context);
tccTransactionManager.confirm();
break;複製程式碼
我們跟蹤 tccTransactionManager.confirm(); 會發現和之前是一個方法,這時候,你要知道,這個方法是在account微服務裡面執行
所以它最後會執行 AccountServiceImpl.confirm 方法,進行了付款確認。