1、Seata做分布式事务时,报错后事务不回滚的问题?
具体问题举例:有两个服务A和服务B,服务A通过openfiegn调用服务B的接口,服务A和服务B都能正常注册到seat服务。接口正常添加@GlobalTransactional注解。方法报错是单个服务内事务均能回滚。但当服务A的serviceA调用服务B的serviceB时,serviceB执行成功并返回结果到serviceA中,然后serviceA报错,些时serviceA能回滚,serviceB不能回滚。
serviceA:
@Override
@GlobalTransactional
public void createTeacherAndStudent() {
System.out.println("事务id---------------------->" + RootContext.getXID());
logger.info("========= 开始调用TEACHER");
Teacher teacher = new Teacher();
teacher.setName("卫国老师");
teacher.setAge(58);
teacher.setEmail("weiguo@teacher.com");
mapper.add(teacher);
mapper.delete(11L);
// 抛出异常
logger.info("========= 开始调用Studen");
logger.info("========= Student将会出现异常");
StudentVo studentVo = new StudentVo();
studentVo.setName("分布式学生");
studentVo.setAge(10);
studentVo.setEmail("1@qq.com");
studentVo.setCreateTime(new Date());
JsonNode add = studentService.addV2("分布式",10,"1@11.com");
logger.info("======== 接收到学生服微服务返回内容:{}", add.toString());
int code = add.get("code").asInt();
String msg = add.get("msg").asText();
// 可以抛出异常 也可以不抛出异常
// 本系统调用其他微服务 其他微服务抛出异常 也会触发分布式事务
if (code != 200) {
throw new BusinessException(code, msg);
}
}
运行后事务ID打印:
事务id---------------------->10.100.6.183:8091:6755797768365363267
serviceB:
@Override
//作为被调用方,加不加这个注解,全局事务都会生效。
@GlobalTransactional
public Long create(StudentVo studentVo) {
System.out.println("事务id---------------------->" + RootContext.getXID());
// 将VO对象转换为POJO对象
BaseConverter<StudentVo, Student> baseConverter = new BaseConverter<>();
Student student = baseConverter.convert(studentVo, Student.class);
mapper.insert(student);
return student.getId();
}
事务id打印结果:
事务id---------------------->10.100.6.183:8091:6755797768365363269
可以看出两次事务id不一致,这就说明两次数据库操作并不是在一个全局事务中,所以造成了问题。我们的问题变为为什么事务id不一致?
一:seat全局事务id是在放在heard中在服务间传递的,然而通过断点,确实在服务B的请求request.header中有事务id。但为什么拿不到?
二: 查看RootContext.getXID()方法源码,发现是从线程变量中取的值,如果没有取到则会新开户一个全局事务。那么问题变成了设置本次请求初始化时设置线程变量出了问题。
三:通过断点分析,正常情况下,服务器接收到请求后,会通过一系列的拦截器列表,其中就有SeataHandlerInterceptor。然而我服务A和服务B启动时初始化的拦截器中根本就没有Seata这个拦截器。那一定是seata配置出了问题,我来来回回对比配置N次就是没有发现配置有什么问题。
四:继续分析Springboot源码,最终发现,正常情况下初始化拦截器会调用DelegatingWebMvcConfiguration 这个类的addInterceptors方法:
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
而我的服务启动却执行的:WebMvcConfigurationSupport类的方法,而这个方法是空方法。而这个类是DelegatingWebMvcConfiguration 的父类。
protected void addInterceptors(InterceptorRegistry registry) {
}
五:最终我发现我的config包里面有几个config类,其中一个是WebMvcConfig.java类。这个类只是用来配置swagger的,而这个类正好是继承至:WebMvcConfigurationSupport。我怀疑就是这里搞的鬼,果不其然,我把WebMvcConfigurationSupport 替换为DelegatingWebMvcConfiguration 后,终于看到两个两个服务都事务回滚了。
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport{
@Resource
ConstantProperties constantProperties;
/**
* 静态资源配置
* @param registry
* @author
* @createTime 2020/07/14
* @description
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String apiDocStatus = "true";
if(apiDocStatus.equalsIgnoreCase(constantProperties.API_DOC)){
// doc文档接口
registry.addResourceHandler("/doc/**").addResourceLocations("classpath:/doc/");
// swagger静态资源配置
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
super.addResourceHandlers(registry);
}
}
}
另一种是实现WebMvcConfigurer接口
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
ConstantProperties constantProperties;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String apiDocStatus = "true";
if(apiDocStatus.equalsIgnoreCase(constantProperties.API_DOC)){
// doc文档接口
registry.addResourceHandler("/doc/**").addResourceLocations("classpath:/doc/");
// swagger静态资源配置
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
}