概述
通过前面的文章介绍,我们已经知道了分布式事务的几种常见的解决方案,从我的从业经历来看,大部分的企业采用TCC或者基于消息队列中间件的最终一致性这两种解决方案。本专栏也将重点剖析这两种方案的具体实现,下面我们先来了解一下分布式环境下微服务实现分布式事务TCC方案的一些基础支持技术。
我们知道TCC是Try、Confirm和Cancel三个阶段的简称,通过下图我们可以看到TCC方案的具体结构图(以用户下单为例)。
可见要想实现TCC分布式解决方案,最重要的一点就是能够在执行Try阶段之后能够根据执行结果回调Confirm方法或者是Cancel方法,并且Confirm和Cancel方法需要开发者自己去实现,而且需要保证幂等性(可接受重复调用结果一致)。
在我们java技术栈中,AOP技术是最容易实现TCC的这种方案回调机制的,当然要想实现TCC,或者说要想实现一个高效稳定可靠易扩展的TCC方案,还需要更多的技术。下面我将写一个demo看看AOP怎样可以实现TCC方案的回调机制,为后面的具体实现提供技术参考思路。
Demo
package com.luke.study.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Descrtption 定义一个TCC注解
* 运用在Try阶段方法上
* @Author luke
* @Date 2019/9/6
**/
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface LukeTCC {
/**confirm阶段方法名*/
String confirmMethod();
/**cancel阶段方法名*/
String cancelMethod();
}
package com.luke.study.annotation;
import java.io.Serializable;
/**
* @Descrtption PaymentReq
* @Author luke
* @Date 2019/9/6
**/
public class PaymentReq implements Serializable {
private Integer number;
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
@Override
public String toString() {
return "PaymentReq{" +
"number=" + number +
'}';
}
}
package com.luke.study.annotation;
import org.springframework.stereotype.Service;
/**
* @Descrtption TestService
* @Author luke
* @Date 2019/9/6
**/
@SuppressWarnings("all")
@Service
public class TestService {
/**
* TCC Try方法
* @param req
*/
@LukeTCC(confirmMethod = "confirmPayment",cancelMethod = "cancelPayment")
public void payment(PaymentReq req){
System.out.println("---service---payment-------"+req);
if(req.getNumber()%2 == 1){
//模拟抛异常
int n = 1/0;
}
}
/**
* TCC confirm方法
* @param req
*/
public void confirmPayment(PaymentReq req){
System.out.println("---service---confirmPayment-------"+req);
}
/**
* TCC cancel方法
* @param req
*/
public void cancelPayment(PaymentReq req){
System.out.println("---service---cancelPayment-------"+req);
}
}
package com.luke.study.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* @Descrtption LukeTCCAspect
* @Author luke
* @Date 2019/9/6
**/
@Component
@Aspect
public class LukeTCCAspect {
@Pointcut("@annotation(com.luke.study.annotation.LukeTCC)")
public void tccTry(){}
/**
* 成功调用完成Try方法
* @param joinPoint
*/
@AfterReturning("tccTry()")
public void afterReturning(JoinPoint joinPoint){
System.out.println("========Try成功调用confirm方法==========");
//获取方法名
String[] methodNames = getTCCConfirmMethoAndCancelMethod(joinPoint);
String confirmMethodName = methodNames[0];
//调用确认方法
callMethod(joinPoint,confirmMethodName);
}
/**
* 调用Try方法出现异常
* @param joinPoint
*/
@AfterThrowing("tccTry()")
public void afterThrowing(JoinPoint joinPoint) {
System.out.println("========Try失败调用cancel方法==========");
//获取方法名
String[] methodNames = getTCCConfirmMethoAndCancelMethod(joinPoint);
String cancelMethodName = methodNames[1];
//调用确认方法
callMethod(joinPoint,cancelMethodName);
}
/**
* 获取TCC确认方法名和取消方法名
* @param joinPoint
* @return
*/
private String[] getTCCConfirmMethoAndCancelMethod(JoinPoint joinPoint){
//获取调用的方法
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
//获取方法的注解内容
LukeTCC lukeTCCAnno = method.getAnnotation(LukeTCC.class);
String confirmMethodName = lukeTCCAnno.confirmMethod();
String cancelMethodName = lukeTCCAnno.cancelMethod();
//封装参数
String[] result = new String[2];
result[0] = confirmMethodName;
result[1] = cancelMethodName;
return result;
}
/**
* 调用方法
* @param joinPoint
* @param methodName
*/
private void callMethod(JoinPoint joinPoint,String methodName){
//获取Try方法对象
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method tryMethod = methodSignature.getMethod();
//请求参数
Object[] args = joinPoint.getArgs();
Parameter[] parameters = tryMethod.getParameters();
Class[] parametersArray = new Class[parameters.length];
for(int i = 0; i < parameters.length; i++){
parametersArray[i] = parameters[i].getType();
}
try {
//调用确认方法
Object target = joinPoint.getTarget();
Class<?> aClass = target.getClass();
Method confirmOrCancelMethod = aClass.getMethod(methodName,parametersArray);
confirmOrCancelMethod.invoke(target,args);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@SpringBootApplication
public class App{
public static void main( String[] args ) {
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
TestService testService = context.getBean(TestService.class);
PaymentReq req = new PaymentReq();
req.setNumber(88);
testService.payment(req);