各位童鞋注意啦,5.0.2的版本在多节点下是存在问题的,解决方法:https://blog.csdn.net/zhuwei_clark/article/details/103711929
项目源码地址:https://github.com/daxian-zhu/online_edu
对于微服务化的趋势,分布式事务是一个绕不去的坎,现在有很多开源的软件 tx-lcn,byte-tcc, seata(阿里系),jta
今天我介绍的是tx-lcn一个国内的开源软件。官网地址:http://www.txlcn.org/zh-cn/
spring boot 1.5X以下的的建议使用4.X的版本。
原因我就不多说了,官网上面又,我直接说代码了。
第一步TM的部署:
新建一个module,并修改POM文件如下:
UTF-8
com.codingapi.txlcn
txlcn-tm
5.0.2.RELEASE
然后新建启动类,新增加注解:EnableTransactionManagerServer
@SpringBootApplication
@EnableTransactionManagerServer
public class LCNApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(LCNApplication.class, args);
}
}
新建application.properties(这里我本来打算用yml的,但是不知道为啥yml配置启动报错)
spring.application.name=TransactionManager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://47.95.250.218:3306/tx_manager?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
spring.datasource.username=lcn
spring.datasource.password=edu.,Dev123
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
#redis配置
spring.redis.host=47.95.250.218
spring.redis.password=zhudaxian;.,68NB
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=60000
#事物执行时间
tx-lcn.manager.dtx-time=600000
注意最后的事物执行时间设置,默认是8000,源码的是这样计算超时时间的
public boolean isDTXTimeout() {
if (!hasTxContext()) {
throw new IllegalStateException("non TxContext.");
}
return (System.currentTimeMillis() - txContext().getCreateTime()) >= clientConfig.getDtxTime();
}
反正我发现这个算法很扯淡,我点了下请求最多几秒,但是这个方法算出来居然30多秒,我就很蛋疼了。所以我先把他设成很大的值,还有如果在client端设置tx-lcn.client.dtx-time这个属性,启动就报错,真不知道这源码咋回事。所以这里在TM这里设置的,TM的设置是会覆盖TC的设置的。
启动TM,访问http://localhost:7970 密码:codingapi 就能进入后台管理界面
到这里TM配置就完事了。
第二步:TC配置,所有的TC配置基本上是一样的,我就介绍一个了
1)修改POM文件
com.codingapi.txlcn
txlcn-tc
5.0.2.RELEASE
com.codingapi.txlcn
txlcn-txmsg-netty
5.0.2.RELEASE
2)修改application.yml配置
#lcn配置
tx-lcn:
client:
manager-address: 127.0.0.1:8070
springcloud:
loadbalance:
enabled: true
集群
且用到 LCN事务模式时,为保证性能请开启TX-LCN重写的负载策略。tx-lcn.springcloud.loadbalance.enabled=true
也就是上面我配置的属性。这是官网上面说的。
3)修改启动类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableResJWTTokenStore //OAuth2 使用 JWT 解析令牌
//开启分布式事务
@EnableDistributedTransaction
public class StudentApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(StudentApplication.class, args);
}
}
增加注解:EnableDistributedTransaction
4)修改业务类
@LcnTransaction(propagation = DTXPropagation.REQUIRED) //分布式事务注解
@Transactional //本地事务注解
@Override
public Result updateStudentInfo(StudentInfo studentInfo) {
if(studentInfo.getId()==null) {
return Result.failure(ResponeCode.FAIL_1001);
}
UC_User uc_User = getUser();
studentInfo.setUpdateUser(uc_User.getId());
studentInfo.setUpdateDate(DateUtil.getNowDateTime());
//更新学生信息
studentInfoDao.updateStudentInfo(studentInfo);
String groupId = TracingContext.tracing().groupId();
String applicationId = Transactions.getApplicationId();
System.out.println(groupId);
System.out.println(applicationId);
//构造客户信息,并更新客户信息
CustomerInfo customer = new CustomerInfo();
customer.setId(studentInfo.getCustomerId());
customer.setAge(studentInfo.getAge());
customer.setSex(studentInfo.getSex());
customer.setName(studentInfo.getName());
customer.setContactMonile(studentInfo.getContactMonile());
customerService.updateCustomerInfo(customer.getId(), customer);
//更新用户信息,暂时不处理
return Result.OK();
}
增加注解@LcnTransaction(propagation = DTXPropagation.REQUIRED) //分布式事务注解 @Transactional //本地事务注解
关于DTXPropagation源码里面有解释
/**
* 当前没有分布式事务,就创建。当前有分布式事务,就加入
*/
REQUIRED,
/**
* 当前没有分布式事务,非分布式事务运行。当前有分布式事务,就加入
*/
SUPPORTS;
所以一般事物发起方使用REQUIRED,事物参与方使用SUPPORTS
5)熔断配置
@Override
public Result updateCustomerInfo(String id, CustomerInfo customerInfo) {
DTXUserControls.rollbackGroup(TracingContext.tracing().groupId());
logger.info("调用{}失败","getCustomerInfo");
return Result.failure(ResponeCode.FAIL_1002);
}
注意增加代码:DTXUserControls.rollbackGroup(TracingContext.tracing().groupId()); 我在这里被坑了好久。
6)配置事物参与方
//事物的参与方
@LcnTransaction(propagation = DTXPropagation.SUPPORTS)
@Transactional
public Result updateCustomerInfo(CustomerInfo customerInfo) {
if(customerInfo.getId()==null) {
return Result.failure(ResponeCode.FAIL_1001);
}
String groupId = TracingContext.tracing().groupId();
String applicationId = Transactions.getApplicationId();
System.out.println(groupId);
System.out.println(applicationId);
groupId = null;
if(groupId.equals("aa")) {
System.out.println("aa");
}
UC_User uc_User = getUser();
customerInfo.setUpdateUser(uc_User.getId());
customerInfo.setUpdateDate(DateUtil.getNowDateTime());
customerInfoDao.updateCustomerInfo(customerInfo);
return Result.OK();
}
这里我设置了一个null值,故意抛出一个空指针。
7)开启各个服务进行测试,我这里student服务是事物发起方(A方),customer服务是事物参与方(B方)
现在是A调用B,B服务会出错,我们看下A和B的事物是不是会回滚
student原始数据状态:
customer原始数据状态:
39ff9b84d54537
customer:9005
2019-07-31 16:27:51.610 ERROR 15420 --- [nio-9005-exec-3] c.c.txlcn.tc.core.DTXServiceExecutor : business code error @group(39ff9b84d54537)
Wed Jul 31 16:27:51 CST 2019 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
2019-07-31 16:27:51.621 ERROR 15420 --- [nio-9005-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null
at com.clark.online_edu.customer.service.impl.CustomerInfoServiceImpl.updateCustomerInfo(CustomerInfoServiceImpl.java:45) ~[classes/:na]
at com.clark.online_edu.customer.service.impl.CustomerInfoServiceImpl$$FastClassBySpringCGLIB$$2be9a8ca.invoke() ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746) ~[spring-aop-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88) ~[spring-aop-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at com.codingapi.txlcn.tc.core.DTXLocalControl.doBusinessCode(DTXLocalControl.java:44) ~[txlcn-tc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at com.codingapi.txlcn.tc.core.DTXServiceExecutor.transactionRunning(DTXServiceExecutor.java:91) ~[txlcn-tc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at com.codingapi.txlcn.tc.aspect.weave.DTXLogicWeaver.runTransaction(DTXLogicWeaver.java:96) ~[txlcn-tc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at com.codingapi.txlcn.tc.aspect.TransactionAspect.runWithLcnTransaction(TransactionAspect.java:93) ~[txlcn-tc-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_101]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_101]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_101]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_101]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644) ~[spring-aop-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633) ~[spring-aop-5.0.9
我们在看A服务:
39ff9b84d54537
student:9006
2019-07-31 16:27:49.518 INFO 5280 --- [nio-9006-exec-5] c.c.o.student.config.FeignConfiguration : 保持请求头
2019-07-31 16:27:51.630 INFO 5280 --- [nio-9006-exec-5] c.c.o.s.s.impl.CustomerServiceImpl : 调用getCustomerInfo失败
进入熔断业务执行正常。接下来我们看下数据库的变化:
student
customer:
我们发现数据都没有变化,数据被回滚了。A没有出现异常数据也回滚了。
8)我们现在注释掉空指针,再次测试,修改customer的代码
//事物的参与方
@LcnTransaction(propagation = DTXPropagation.SUPPORTS)
@Transactional
public Result updateCustomerInfo(CustomerInfo customerInfo) {
if(customerInfo.getId()==null) {
return Result.failure(ResponeCode.FAIL_1001);
}
String groupId = TracingContext.tracing().groupId();
String applicationId = Transactions.getApplicationId();
System.out.println(groupId);
System.out.println(applicationId);
// groupId = null;
// if(groupId.equals("aa")) {
// System.out.println("aa");
// }
UC_User uc_User = getUser();
customerInfo.setUpdateUser(uc_User.getId());
customerInfo.setUpdateDate(DateUtil.getNowDateTime());
customerInfoDao.updateCustomerInfo(customerInfo);
return Result.OK();
}
发起请求测试:
student
customer: