此分布式框架为TCC(try、confirm、cancel):是最终一致性事务解决方案,也就是所谓的柔性事务。
一:一切的开始始于 HmilyTransactionBootstrap
由于实现类 ApplicationContextAware 接口,因此在 context容器启动后,会立即执行其 setApplicationContext()方法,如下:
* 实现ApplicationContextAware 接口,会在容器初始化的时候,带入 ApplicationContext 容器进来
* 这里也就是在容器启动后直接进行初始化工作,因此此处是一切开始的地方
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
//将 applicationContext设置进 SpringBeanUtils中,通过此utils调用 ioc中的 bean
SpringBeanUtils.getInstance().setCfgContext((ConfigurableApplicationContext) applicationContext);
start(this);
}
SpringBeanUtils是一个把类设置进context容器,以及从容器中取值的一个工具类,具体见 Hmily
之后 start()中调用 HmilyInitService 类,如下:
/**
*
* @FunctionalInterface:java8函数式接口,用于函数式编程,只能有一个public方法(default除外)
*
* 使用例子:
* public interface XttblogService {
* void sayMessage(String message);
* }
*
* 那么我们现在就可以使用 Lambda 表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
* XttblogService xttblogService = message -> System.out.println("Hello " + message);
*
*/
@FunctionalInterface
public interface HmilyInitService {
调用其方法:
/**
* hmily initialization.
* @param hmilyConfig {@linkplain HmilyConfig}
*
* 解析:Runtime.getRuntime().addShutdownHook: 在JVM销毁前执行一个线程,如可以在此线程中优雅的关闭线程池
*
*/
@Override
public void initialization(final HmilyConfig hmilyConfig) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> LOGGER.info("hmily shutdown now")));
try {
//通过spi自定义注解来获取具体要实现的子类
loadSpiSupport(hmilyConfig);
hmilyCoordinatorService.start(hmilyConfig);
} catch (Exception ex) {
LogUtil.error(LOGGER, " hmily init exception:{}", ex::getMessage);
System.exit(1);
}
//自定义图标:其实就是定义图标然后打印如:log.info这种打印出来即可
new HmilyLogo().logo();
}
SPI:非常有用的一种实例化子类的方法,类似于 策略模式:
/**
* load spi.
*
* @param hmilyConfig {@linkplain HmilyConfig}
*/
private void loadSpiSupport(final HmilyConfig hmilyConfig) {
//spi serialize
final ObjectSerializer serializer = ExtensionLoader.getExtensionLoader(ObjectSerializer.class)
.getActivateExtension(hmilyConfig.getSerializer());
//spi repository private String hmilyConfig.repositorySupport = "db";
final HmilyCoordinatorRepository repository = ExtensionLoader.getExtensionLoader(HmilyCoordinatorRepository.class)
.getActivateExtension(hmilyConfig.getRepositorySupport());
repository.setSerializer(serializer);
//把实例化的HmilyCoordinatorRepository实现类注入到context中
SpringBeanUtils.getInstance().registerBean(HmilyCoordinatorRepository.class.getName(), repository);
}
此处 HmilyCoordinatorRepository 默认初始化策略为 "db",因此 会通过spi去加载对应的 db实现类,具体:
ExtensionLoader :
/**
* 例:A.isAnnotationPresent(B.class);
* B类型的注解是否在A类上。
* @param type
* @param <T>
* @return
*/
private static <T> boolean withExtensionAnnotation(final Class<T> type) {
//判断传入的类上是否含有 HmilySPI的注解
return type.isAnnotationPresent(HmilySPI.class);
}
/**
* Gets activate extension.
*
* @param value the value
* @return the activate extension
*
* private static final String PREFIX = "META-INF/services/";
* 注:ServiceLoader:如果某个接口含有一系列实现类,此时 loadAll(type) 里面放入此父类的class如: HmilyCoordinatorRepository.class
* 再在 META-INF下面建一个 services文件夹,里面放入所有实现类的名字如:
*
* org.dromara.hmily.core.spi.repository.FileCoordinatorRepository
* org.dromara.hmily.core.spi.repository.JdbcCoordinatorRepository
* 此时调用 loadAll()方法就会获取到所有的子类,由此来实现参数的动态配置,面向对象,前提是这些类已经定义好,并且是其子类。
*
* kryo
*/
public T getActivateExtension(final String value) {
ServiceLoader<T> loader = ServiceBootstrap.loadAll(type);
return StreamSupport.stream(loader.spliterator(), false)
.filter(e -> Objects.equals(e.getClass()
.getAnnotation(HmilySPI.class).value(), value))
.findFirst().orElseThrow(() -> new HmilyException("Please check your configuration"));
}
形式:
获取事务保存方式为:JdbcCoordinatorRepository ,然后进行数据库初始化建表等操作:
@Override
public void start(final HmilyConfig hmilyConfig) {
final String repositorySuffix = buildRepositorySuffix(hmilyConfig.getRepositorySuffix());
//获取实例化类型
coordinatorRepository = SpringBeanUtils.getInstance().getBean(HmilyCoordinatorRepository.class);
//获取对应的数据库类型然后建表
coordinatorRepository.init(repositorySuffix, hmilyConfig);
}
/**
* jdbc类型初始化,主要是为了根据传入的配置进行建表操作
* @param modelName modelName
* @param txConfig
*/
@Override
public void init(final String modelName, final HmilyConfig txConfig) {
try {
final HmilyDbConfig hmilyDbConfig = txConfig.getHmilyDbConfig();
if (hmilyDbConfig.getDataSource() != null && StringUtils.isBlank(hmilyDbConfig.getUrl())) {
dataSource = hmilyDbConfig.getDataSource();
} else {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl(hmilyDbConfig.getUrl());
hikariDataSource.setDriverClassName(hmilyDbConfig.getDriverClassName());
hikariDataSource.setUsername(hmilyDbConfig.getUsername());
hikariDataSource.setPassword(hmilyDbConfig.getPassword());
hikariDataSource.setMaximumPoolSize(hmilyDbConfig.getMaxActive());
hikariDataSource.setMinimumIdle(hmilyDbConfig.getMinIdle());
hikariDataSource.setConnectionTimeout(hmilyDbConfig.getConnectionTimeout());
hikariDataSource.setIdleTimeout(hmilyDbConfig.getIdleTimeout());
hikariDataSource.setMaxLifetime(hmilyDbConfig.getMaxLifetime());
hikariDataSource.setConnectionTestQuery(hmilyDbConfig.getConnectionTestQuery());
if (hmilyDbConfig.getDataSourcePropertyMap() != null && !hmilyDbConfig.getDataSourcePropertyMap().isEmpty()) {
hmilyDbConfig.getDataSourcePropertyMap().forEach(hikariDataSource::addDataSourceProperty);
}
dataSource = hikariDataSource;
}
this.tableName = RepositoryPathUtils.buildDbTableName(modelName);
//save current database type
this.currentDBType = DbTypeUtils.buildByDriverClassName(hmilyDbConfig.getDriverClassName());
executeUpdate(SqlHelper.buildCreateTableSql(hmilyDbConfig.getDriverClassName(), tableName));
} catch (Exception e) {
LogUtil.error(LOGGER, "hmily jdbc log init exception please check config:{}", e::getMessage);
throw new HmilyRuntimeException(e);
}
}
注:如果要自己定义表,使用 executeUpdate()原生jdbc连接操作:
private int executeUpdate(final String sql, final Object... params) {
Connection connection = null;
PreparedStatement ps = null;
try {
connection = dataSource.getConnection();
ps = connection.prepareStatement(sql);
if (params != null) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, convertDataTypeToDB(params[i]));
}
}
return ps.executeUpdate();
} catch (SQLException e) {
LOGGER.error("executeUpdate-> " + e.getMessage());
return FAIL_ROWS;
} finally {
close(connection, ps, null);
}
}
底层使用的connection还是 connection = dataSource.getConnection(); 而springBoot默认dataSource是 hikariDataSource,dataSource = hikariDataSource;
二:启动 Disruptor
*
* 实现 ApplicationListener<ContextRefreshedEvent> 接口,可以在容器一启动的时候就调用
*
*/
@Component
public class StarterHmilyTransactionHandler implements HmilyTransactionHandler, ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
if (hmilyConfig.getStarted()) {
disruptorProviderManage = new DisruptorProviderManage<>(new HmilyConsumerTransactionDataHandler(),
hmilyConfig.getAsyncThreads(),
DisruptorProviderManage.DEFAULT_SIZE);
disruptorProviderManage.startup();
}
}
事务核心:
@Override
public Object handler(final ProceedingJoinPoint point, final HmilyTransactionContext context)
throws Throwable {
Object returnValue;
try {//此处preTry是try之前的操作,封装了当前transaction,保存的participate就只有当前的主方法的comfirm以及cancel方法
HmilyTransaction hmilyTransaction = hmilyTransactionExecutor.preTry(point);
try {
//execute try
returnValue = point.proceed();
//try阶段完成
hmilyTransaction.setStatus(HmilyActionEnum.TRYING.getCode());
//把transaction发布为生产者
hmilyTransactionExecutor.updateStatus(hmilyTransaction);
} catch (Throwable throwable) {
//此处代表整个事务失败后进行的取消
//if exception ,execute cancel 并且从缓存中删除当前已经取消了的缓存,同时把消费者中的状态置为删除
final HmilyTransaction currentTransaction = hmilyTransactionExecutor.getCurrentTransaction();
disruptorProviderManage.getProvider().onData(() -> hmilyTransactionExecutor.cancel(currentTransaction));
throw throwable;
}
//此处代表整个事务成功后的确认提交 execute confirm
final HmilyTransaction currentTransaction = hmilyTransactionExecutor.getCurrentTransaction();
disruptorProviderManage.getProvider().onData(() -> hmilyTransactionExecutor.confirm(currentTransaction));
} finally {
HmilyTransactionContextLocal.getInstance().remove();
hmilyTransactionExecutor.remove();
}
return returnValue;
}
关键:获取一个方法的全部信息,然后通过反射调用方法,进行补偿:
/**
* 通过反射获取的类、方法名、参数、参数类型来进行方法调用
* @param hmilyInvocation
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
private static Object execute(final HmilyInvocation hmilyInvocation) throws Exception {
if (Objects.isNull(hmilyInvocation)) {
return null;
}
final Class clazz = hmilyInvocation.getTargetClass();
final String method = hmilyInvocation.getMethodName();
final Object[] args = hmilyInvocation.getArgs();
final Class[] parameterTypes = hmilyInvocation.getParameterTypes();
final Object bean = SpringBeanUtils.getInstance().getBean(clazz);
return MethodUtils.invokeMethod(bean, method, args, parameterTypes);
}
而这个方法的封装类:
/**
* TccInvocation 封装方法调用点.
* @author xiaoyu
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HmilyInvocation implements Serializable {
private static final long serialVersionUID = -5108578223428529356L;
@Getter
private Class targetClass;
@Getter
private String methodName;
@Getter
private Class[] parameterTypes;
@Getter
private Object[] args;
}
三:最核心的是 dubbo协议的调用:
/**
* impl dubbo filter.
*
* 实现 dubbo的 filter接口,当进行 dubbo rpc调用时,会调用invoke方法,进行后续处理
*
* @author xiaoyu
*/
@Activate(group = {Constants.SERVER_KEY, Constants.CONSUMER})
@SuppressWarnings("all")
public class DubboHmilyTransactionFilter implements Filter {
@Override
public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
String methodName = invocation.getMethodName();
Class clazz = invoker.getInterface();
Class[] args = invocation.getParameterTypes();
final Object[] arguments = invocation.getArguments();
Method method = null;
Hmily hmily = null;
try {
converterParamsClass(args, arguments);
method = clazz.getMethod(methodName, args);
hmily = method.getAnnotation(Hmily.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Exception ex) {
ex.printStackTrace();
LogUtil.error(LOGGER, "hmily find method error {} ", ex::getMessage);
}
if (Objects.nonNull(hmily)) {
try {
final HmilyTransactionContext hmilyTransactionContext = HmilyTransactionContextLocal.getInstance().get();
if (Objects.nonNull(hmilyTransactionContext)) {
RpcMediator.getInstance().transmit(RpcContext.getContext()::setAttachment, hmilyTransactionContext);
final Result result = invoker.invoke(invocation);
//if result has not exception
if (!result.hasException()) {
//构建参与者信息 Participant
final HmilyParticipant hmilyParticipant = buildParticipant(hmilyTransactionContext, hmily, method, clazz, arguments, args);
if (hmilyTransactionContext.getRole() == HmilyRoleEnum.INLINE.getCode()) {
hmilyTransactionExecutor.registerByNested(hmilyTransactionContext.getTransId(),
hmilyParticipant);
} else {
hmilyTransactionExecutor.enlistParticipant(hmilyParticipant);
}
} else {
throw new HmilyRuntimeException("rpc invoke exception{}", result.getException());
}
return result;
}
return invoker.invoke(invocation);
} catch (RpcException e) {
e.printStackTrace();
throw e;
}
} else {
return invoker.invoke(invocation);
}
}
注:下面是如何获取反射需要的信息:
@Override
public Object handler(final ProceedingJoinPoint point, final HmilyTransactionContext context) throws Throwable {
if (HmilyActionEnum.TRYING.getCode() == context.getAction()) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Class<?> clazz = point.getTarget().getClass();
Object[] args = point.getArgs();
final Hmily hmily = method.getAnnotation(Hmily.class);
HmilyInvocation confirmInvocation = null;
String confirmMethodName = hmily.confirmMethod();
String cancelMethodName = hmily.cancelMethod();
if (StringUtils.isNoneBlank(confirmMethodName)) {
confirmInvocation = new HmilyInvocation(clazz, confirmMethodName, method.getParameterTypes(), args);
}
HmilyInvocation cancelInvocation = null;
if (StringUtils.isNoneBlank(cancelMethodName)) {
cancelInvocation = new HmilyInvocation(clazz, cancelMethodName, method.getParameterTypes(), args);
}
final HmilyParticipant hmilyParticipant = new HmilyParticipant(context.getTransId(),
confirmInvocation, cancelInvocation);
hmilyTransactionExecutor.registerByNested(context.getTransId(), hmilyParticipant);
}
return point.proceed();
}