TCC是什么:
TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try、确认 Confirm、撤销Cancel。Try操作做业务检查及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的 操作即回滚操作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所 有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel 操作若执行失败,TM会进行重试。
分布式事务执行成功:
这边try执行成功后,没有异常会直接进入Confirm确认提交阶段。
分布式执行失败:
这边try执行失败后,执行成功一方的资源会进行事务回滚。
TCC分为三个阶段:
1:Try 阶段是做业务检查一致性)及资源预留隔离),此阶段仅是一个初步操作,它和后续的Confirm 一起才能 真正构成一个完整的业务逻辑。
2. Confirm 阶段是做确认提交,Try阶段所有分支事务执行成功后开始执行 Confirm。通常情况下,采用TCC则 认为 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。若Confirm阶段真的出错了,需引 入重试机制或人工处理。
3. Cancel 阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放。通常情况下,采 用TCC则认为Cancel阶段也是一定成功的。若Cancel阶段真的出错了,需引入重试机制或人工处理。
4. TM事务管理器 TM事务管理器可以实现为独立的服务,也可以让全局事务发起方充当TM的角色,TM独立出来是为了成为公 用组件,是为了考虑系统结构和软件复用。TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条,用来记录事务上下文, 追踪和记录状态,由于Confirm 和cancel失败需进行重试,因此需要实现为幂等,幂等性是指同一个操作无论请求 多少次,其结果都相同。
Hmily:
Hmily是一个高性能分布式事务TCC开源框架。基于Java语言来开发(JDK1.8),支持Dubbo,Spring Cloud等 RPC框架进行分布式事务。Hmily实现的TCC服务与普通的服务一样,只需要暴露一个接口,也就是它的Try业务。Confirm/Cancel业务 逻辑,只是因为全局事务提交/回滚的需要才提供的,因此Confirm/Cancel业务只需要被Hmily TCC事务框架 发现即可,不需要被调用它的其他业务服务所感知。
官网介绍:https://dromara.org/website/zh-cn/docs/hmily/index.html
Hmily环境构建:
首先导入hmily:
<dependency>
<groupId>org.dromara</groupId>
<artifactId>hmily-springcloud</artifactId>
<version>2.0.4-RELEASE</version>
</dependency>
添加hmily数据库日志:
package com.study1.conf;
import com.alibaba.druid.pool.DruidDataSource;
import org.dromara.hmily.common.config.HmilyConfig;
import org.dromara.hmily.common.config.HmilyDbConfig;
import org.dromara.hmily.core.bootstrap.HmilyTransactionBootstrap;
import org.dromara.hmily.core.service.HmilyInitService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.env.Environment;
@Configuration
@EnableAspectJAutoProxyproxyTargetClass=true)
public class DatabaseHmliyConfiguration {
private final ApplicationContext applicationContext;
@Autowired
private Environment env;
public DatabaseHmliyConfigurationApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
@ConfigurationPropertiesprefix = "spring.datasource")
public DruidDataSource ds0) {
DruidDataSource druidDataSource = new DruidDataSource);
return druidDataSource;
}
@Bean
public HmilyTransactionBootstrap hmilyTransactionBootstrapHmilyInitService hmilyInitService){
HmilyTransactionBootstrap hmilyTransactionBootstrap = new HmilyTransactionBootstraphmilyInitService);
hmilyTransactionBootstrap.setSerializerenv.getProperty"org.dromara.hmily.serializer"));
hmilyTransactionBootstrap.setRecoverDelayTimeInteger.parseIntenv.getProperty"org.dromara.hmily.recoverDelayTime")));
hmilyTransactionBootstrap.setRetryMaxInteger.parseIntenv.getProperty"org.dromara.hmily.retryMax")));
hmilyTransactionBootstrap.setScheduledDelayInteger.parseIntenv.getProperty"org.dromara.hmily.scheduledDelay")));
hmilyTransactionBootstrap.setScheduledThreadMaxInteger.parseIntenv.getProperty"org.dromara.hmily.scheduledThreadMax")));
hmilyTransactionBootstrap.setRepositorySupportenv.getProperty"org.dromara.hmily.repositorySupport"));
hmilyTransactionBootstrap.setStartedBoolean.parseBooleanenv.getProperty"org.dromara.hmily.started")));
HmilyDbConfig hmilyDbConfig = new HmilyDbConfig);
hmilyDbConfig.setDriverClassNameenv.getProperty"org.dromara.hmily.hmilyDbConfig.driverClassName"));
hmilyDbConfig.setUrlenv.getProperty"org.dromara.hmily.hmilyDbConfig.url"));
hmilyDbConfig.setUsernameenv.getProperty"org.dromara.hmily.hmilyDbConfig.username"));
hmilyDbConfig.setPasswordenv.getProperty"org.dromara.hmily.hmilyDbConfig.password"));
hmilyTransactionBootstrap.setHmilyDbConfighmilyDbConfig);
return hmilyTransactionBootstrap;
}
}
添加yml配置:
org:
dromara:
hmily:
serializer: kryo
recoverDelayTime: 30
retryMax: 30
scheduledDelay: 30
scheduledThreadMax: 10
repositorySupport: db
started: true
hmilyDbConfig:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/bank1?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: root
password: 123
添加扫描:
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients //使用@feign
//@EnableHystrix //使用hystrix
@ComponentScan{
"com.study1","org.dromara.hmily"})
@MapperScan"com.study1.mapper")
public class ServceBank1Application {
public static void mainString[] args) {
SpringApplication.runServceBank1Application.class, args);
}
}
第二个服务同上
这边数据库采用bank1,bank2
bank1实现用户扣款功能:
//--------------Hmily------------------
//只要标记@Hmily就是try方法,在注解中指定confirm、cancel两个方法的名字
@Override
@Transactional
@HmilyconfirmMethod = "commit", cancelMethod = "rollback")
public boolean reduceHmilyAmountint amount) {
//预处理->扣款 张三扣款成功
if bankMapper.reduceAmountamount) > 0) {
System.out.println"开始调用远程服务");
//李四添加金额
bankFeignService.addAmountamount);
return true;
} else {
return false;
}
}
@Transactional
public void commitint amount) {
System.out.println"成功处理事务");
}
@Transactional
public void rollbackint amount) {
System.out.println"失败处理事务");
}
bank2实现用户添加余额的功能:
//--------------Hmily------------------
//只要标记@Hmily就是try方法,在注解中指定confirm、cancel两个方法的名字
@Override
@Transactional
@HmilyconfirmMethod = "commit", cancelMethod = "rollback")
public void addAmountInteger amount) {
//预处理->扣款 张三扣款成功
bankMapper.addAmountamount);
System.out.println"调用完成");
int y = 3 / 0;
System.out.println"执行其他业务");
}
@Transactional
public void commitint amount) {
System.out.println"bank2成功处理事务");
}
@Transactional
public void rollbackint amount) {
System.out.println"---------------------失败处理事务------------------------");
System.out.println"---------------------失败处理事务------------------------");
System.out.println"---------------------失败处理事务------------------------");
System.out.println"---------------------失败处理事务------------------------");
}
feign服务调用:
//可以捕获错误日志
@FeignClientvalue = "bank2", fallback = BankFeignServiceImpl.class)
public interface BankFeignService {
@GetMapping"/bank2/addAmount")
@Hmily
public boolean addAmount@RequestParam"amount") Integer amount);
}
采用的是feign进行微服务调用,测试调用bank2报错,bank1执行回调方法:
开始调用远程服务
feign.FeignException: status 500 reading BankFeignService#addAmountInteger)
at feign.FeignException.errorStatusFeignException.java:78)
at feign.codec.ErrorDecoder$Default.decodeErrorDecoder.java:93)
at feign.SynchronousMethodHandler.executeAndDecodeSynchronousMethodHandler.java:149)
at feign.SynchronousMethodHandler.invokeSynchronousMethodHandler.java:78)
at feign.ReflectiveFeign$FeignInvocationHandler.invokeReflectiveFeign.java:103)
at com.sun.proxy.$Proxy133.addAmountUnknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0Native Method)
at sun.reflect.NativeMethodAccessorImpl.invokeNativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invokeDelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invokeMethod.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflectionAopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpointReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceedReflectiveMethodInvocation.java:163)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invokeExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceedReflectiveMethodInvocation.java:186)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceedMethodInvocationProceedingJoinPoint.java:88)
at org.dromara.hmily.core.service.handler.ConsumeHmilyTransactionHandler.handlerConsumeHmilyTransactionHandler.java:35)
at org.dromara.hmily.core.service.impl.HmilyTransactionAspectServiceImpl.invokeHmilyTransactionAspectServiceImpl.java:63)
at org.dromara.hmily.springcloud.interceptor.SpringCloudHmilyTransactionInterceptor.interceptorSpringCloudHmilyTransactionInterceptor.java:78)
at org.dromara.hmily.core.interceptor.AbstractHmilyTransactionAspect.interceptTccMethodAbstractHmilyTransactionAspect.java:61)
at sun.reflect.NativeMethodAccessorImpl.invoke0Native Method)
at sun.reflect.NativeMethodAccessorImpl.invokeNativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invokeDelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invokeMethod.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgsAbstractAspectJAdvice.java:644)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodAbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invokeAspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceedReflectiveMethodInvocation.java:175)
at org.springframework.aop.framework.JdkDynamicAopProxy.invokeJdkDynamicAopProxy.java:212)
at org.dromara.hmily.springcloud.feign.HmilyFeignHandler.invokeHmilyFeignHandler.java:62)
at com.sun.proxy.$Proxy134.addAmountUnknown Source)
at com.study1.service.impl.BankServiceImpl.reduceHmilyAmountBankServiceImpl.java:52)
at com.study1.service.impl.BankServiceImpl$$FastClassBySpringCGLIB$$b1ddc6fd.invoke<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeMethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpointCglibAopProxy.java:749)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceedReflectiveMethodInvocation.java:163)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransactionTransactionAspectSupport.java:294)
at org.springframework.transaction.interceptor.TransactionInterceptor.invokeTransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceedReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invokeExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceedReflectiveMethodInvocation.java:186)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceedMethodInvocationProceedingJoinPoint.java:88)
at org.dromara.hmily.core.service.handler.StarterHmilyTransactionHandler.handlerStarterHmilyTransactionHandler.java:70)
at org.dromara.hmily.core.service.impl.HmilyTransactionAspectServiceImpl.invokeHmilyTransactionAspectServiceImpl.java:63)
at org.dromara.hmily.springcloud.interceptor.SpringCloudHmilyTransactionInterceptor.interceptorSpringCloudHmilyTransactionInterceptor.java:78)
at org.dromara.hmily.core.interceptor.AbstractHmilyTransactionAspect.interceptTccMethodAbstractHmilyTransactionAspect.java:61)
at sun.reflect.NativeMethodAccessorImpl.invoke0Native Method)
at sun.reflect.NativeMethodAccessorImpl.invokeNativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invokeDelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invokeMethod.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgsAbstractAspectJAdvice.java:644)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodAbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invokeAspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceedReflectiveMethodInvocation.java:175)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.interceptCglibAopProxy.java:688)
at com.study1.service.impl.BankServiceImpl$$EnhancerBySpringCGLIB$$e3476692.reduceHmilyAmount<generated>)
at com.study1.controller.BankController.reduceAmountBankController.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0Native Method)
at sun.reflect.NativeMethodAccessorImpl.invokeNativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invokeDelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invokeMethod.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvokeInvocableHandlerMethod.java:189)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequestInvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandleServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethodRequestMappingHandlerAdapter.java:892)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternalRequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handleAbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatchDispatcherServlet.java:1038)
at org.springframework.web.servlet.DispatcherServlet.doServiceDispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequestFrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doPostFrameworkServlet.java:908)
at javax.servlet.http.HttpServlet.serviceHttpServlet.java:660)
at org.springframework.web.servlet.FrameworkServlet.serviceFrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.serviceHttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilterApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilterApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilterWsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilterApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilterApplicationFilterChain.java:166)
at com.alibaba.druid.support.http.WebStatFilter.doFilterWebStatFilter.java:123)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilterApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilterApplicationFilterChain.java:166)
at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternalHttpTraceFilter.java:90)
at org.springframework.web.filter.OncePerRequestFilter.doFilterOncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilterApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilterApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternalRequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilterOncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilterApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilterApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternalFormContentFilter.java:92)
at org.springframework.web.filter.OncePerRequestFilter.doFilterOncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilterApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilterApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternalHiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilterOncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilterApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilterApplicationFilterChain.java:166)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetricsWebMvcMetricsFilter.java:117)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternalWebMvcMetricsFilter.java:106)
at org.springframework.web.filter.OncePerRequestFilter.doFilterOncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilterApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilterApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternalCharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilterOncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilterApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilterApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invokeStandardWrapperValve.java:200)
at org.apache.catalina.core.StandardContextValve.invokeStandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invokeAuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invokeStandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invokeErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invokeStandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.serviceCoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.serviceHttp11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.processAbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.processAbstractProtocol.java:834)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRunNioEndpoint.java:1415)
at org.apache.tomcat.util.net.SocketProcessorBase.runSocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorkerThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.runThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.runTaskThread.java:61)
at java.lang.Thread.runThread.java:748)
失败处理事务
使用Hmily注意事项:
在使用Hmliy时候注意里面的try,confirm,cancel等方法都是异步线程调用,注意避免他们进行频繁调用可以在数据库中添加方法调用记录,避免重复调用导致数据不一致的情况。同时当事务处理失败未得到处理时,数据库表中有一个hmily的日志记录表,它会对日志中的记录进行定时重复调用。