跳至主要內容

Seata 全局事务不生效问题

Zenghr大约 2 分钟JavaJava

Seata 全局事务不生效问题

提示

记录工作中遇到到 Seata 全局事务不回滚的问题

开发环境

  • Spring Boot
  • Seata 分布式事务解决方案

实际场景

在工作中遇到需要调用其他服务接口,由于项目结构问题,没有使用其他的框架去调用接口,而是自己写 HTTP 请求接口调用

在事务发起方贴上注解 @GlobalTransactional 启动 Seata 全局事务,发生错误后事务回滚

但是在测试时发现出现异常时 被调用的服务并没有回滚事务

问题解决

通过查看日志发现被调用服务并没有启动全局事务,百思不得其解,一开始以为是因为配置了全局异常拦截器导致 Seata 并没有捕获到异常也就没有回滚事务,一顿折腾才发现不是这个原因

后面想到跨服务调用的事务传播是通过 传递事务 ID来开启的,那么会不会是事务 ID 没有传过来

跨服务调用场景下的事务传播,本质上就是把 XID 通过服务调用传递到服务提供方,并绑定到 RootContext 中

因为我是自己封装 HTTP 请求调用,所以只需要把 XID 放到请求中,那么事务就可以启动了

// 添加事务 ID
headers.add(RootContext.KEY_XID, RootContext.getXID());

后面也是去了解了一下 SeataHandlerInterceptor 中的源码,就可以发现拦截器也是直接获取请求中的 XID,所以说理论上只要把事务 ID 提供出来,Seata 可以支持任意的微服务框架

public class SeataHandlerInterceptor implements HandlerInterceptor {
	private static final Logger log = LoggerFactory.getLogger(SeataHandlerInterceptor.class);

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
		
        String xid = RootContext.getXID();
        // 这里获取 请求中事务 ID
		String rpcXid = request.getHeader(RootContext.KEY_XID);
		if (log.isDebugEnabled()) {
			log.debug("xid in RootContext {} xid in RpcContext {}", xid, rpcXid);
		}

		if (StringUtils.isBlank(xid) && rpcXid != null) {
            // 如果事务ID为空 直接绑定
			RootContext.bind(rpcXid);
			if (log.isDebugEnabled()) {
				log.debug("bind {} to RootContext", rpcXid);
			}
		}
		return true;
	}
}