Seata分布式事务的事务传播之源码分析

事务上下文

Seata 的事务上下文由 RootContext 来管理。
应用开启一个全局事务后,RootContext 会自动绑定该事务的 XID,事务结束(提交或回滚完成),RootContext 会自动解绑 XID。

/**
     * 事务的全局开启方法
     * @param timeout Given timeout in MILLISECONDS.
     * @param name    Given name.
     * @throws TransactionException
     */
    @Override
    public void begin(int timeout, String name) throws TransactionException {
        if (role != GlobalTransactionRole.Launcher) {
            check();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Ignore Begin(): just involved in global transaction [{}]", xid);
            }
            return;
        }
        if (xid != null) {
            throw new IllegalStateException();
        }
        if (RootContext.getXID() != null) {
            throw new IllegalStateException();
        }
        //开启事务,服务端返回全局事务的唯一标识,然后进行绑定事务,这个xid会传递到后面的所有分支事务
        xid = transactionManager.begin(null, null, name, timeout);
        status = GlobalStatus.Begin;
        // 绑定 XID
        RootContext.bind(xid);
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Begin new global transaction [{}]", xid);
        }

    }

服务端的接收到事务的处理开启请求的处理方法

    /**
     * 事务开启
     * @param applicationId           ID of the application who begins this transaction.
     * @param transactionServiceGroup ID of the transaction service group.
     * @param name                    Give a name to the global transaction.
     * @param timeout                 Timeout of the global transaction.
     * @return
     * @throws TransactionException
     */
    @Override
    public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
        throws TransactionException {
        //创建一个全局的会话,也就是创建一个全局的XID
        GlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name,
            timeout);
        session.addSessionLifecycleListener(SessionHolder.getRootSessionManager());

        session.begin();

        //transaction start event
        //事务开启事件
        eventBus.post(new GlobalTransactionEvent(session.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
            session.getTransactionName(), session.getBeginTime(), null, session.getStatus()));

        LOGGER.info("Successfully begin global transaction xid = {}", session.getXid());
        //返回XID给客户端
        return session.getXid();
    }
    /**
     * 进行发送请求,获取XID
     * @param applicationId           ID of the application who begins this transaction.
     * @param transactionServiceGroup ID of the transaction service group.
     * @param name                    Give a name to the global transaction.
     * @param timeout                 Timeout of the global transaction.
     * @return
     * @throws TransactionException
     */
    @Override
    public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
        throws TransactionException {
        GlobalBeginRequest request = new GlobalBeginRequest();
        request.setTransactionName(name);
        request.setTimeout(timeout);
        //向服务端TC发送请求,获取到XID
        GlobalBeginResponse response = (GlobalBeginResponse)syncCall(request);
        if (response.getResultCode() == ResultCode.Failed) {
            throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
        }
        //返回XID,提交和回滚的时候会根据这个xid查到会话
        return response.getXid();
    }

事务传播

Seata 全局事务的传播机制就是指事务上下文的传播,从根本上,就是 XID 的应用运行时的传播方式。

  1. 服务内部的事务传播
/**
 * The type Thread local context core.
 * 默认的,RootContext 的实现是基于 ThreadLocal 的,即 XID 绑定在当前线程上下文中。
 * ThreadLocalContextCore 内部封装了一个ThreadLocal线程内部对象
 */
@LoadLevel(name = "ThreadLocalContextCore", order = Integer.MIN_VALUE)
public class ThreadLocalContextCore implements ContextCore {

    private ThreadLocal<Map<String, String>> threadLocal = new ThreadLocal<Map<String, String>>() {
        @Override
        protected Map<String, String> initialValue() {
            return new HashMap<String, String>();
        }

    };

    @Override
    public String put(String key, String value) {
        return threadLocal.get().put(key, value);
    }

    @Override
    public String get(String key) {
        return threadLocal.get().get(key);
    }

    @Override
    public String remove(String key) {
        return threadLocal.get().remove(key);
    }

    @Override
    public Map<String, String> entries() {
        return threadLocal.get();
    }
}

所以说服务内部的 XID 传播通常是天然的通过同一个线程的调用链路串连起来的。默认不做任何处理,事务的上下文就是传播下去的。
如果希望挂起事务上下文,则需要通过 RootContext 提供的 API 来实现:

// 挂起(暂停)
String xid = RootContext.unbind();
// TODO: 运行在全局事务外的业务逻辑
// 恢复全局事务上下文
RootContext.bind(xid);

比如Spring cloud alibaba 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) {
		//获取全局事务的唯一标识,这个是XID是从服务器传过来的
		String xid = RootContext.getXID();
		String rpcXid = request.getHeader(RootContext.KEY_XID);
		if (log.isDebugEnabled()) {
			log.debug("xid in RootContext {} xid in RpcContext {}", xid, rpcXid);
		}
		if (xid == null && rpcXid != null) {
			//把事务绑定到当前线程当中,这样在同一个服务内容,他的事务是传递的
			RootContext.bind(rpcXid);
			if (log.isDebugEnabled()) {
				log.debug("bind {} to RootContext", rpcXid);
			}
		}
		return true;
	}
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
			Object handler, Exception e) {

		String rpcXid = request.getHeader(RootContext.KEY_XID);
		if (StringUtils.isEmpty(rpcXid)) {
			return;
		}
		//调用完成之后,则挂起事务
		String unbindXid = RootContext.unbind();
		if (log.isDebugEnabled()) {
			log.debug("unbind {} from RootContext", unbindXid);
		}
		if (!rpcXid.equalsIgnoreCase(unbindXid)) {
			log.warn("xid in change during RPC from {} to {}", rpcXid, unbindXid);
			if (unbindXid != null) {
				RootContext.bind(unbindXid);
				log.warn("bind {} back to RootContext", unbindXid);
			}
		}
	}

}
  1. 跨服务调用的事务传播
    跨服务调用场景下的事务传播,本质上就是要把 XID 通过服务调用传递到服务提供方,并绑定到 RootContext 中去。
    Spring Cloud的跨服务调用主要提供了二种方式进行跨服务调用,分别是Fegin和RestTemplate的方式进行跨服务调用,所以在Spring Cloud alibaba seata当中也提供了这种的事务传递方式,下面具体来分析具体的代码,项目结构如下
    在这里插入图片描述
    feign目录主要是当我们进行跨服务调用方式使用的是feign客户端的方法时,就会被这里的自动配置启动的拦截器给拦截到,rest目录放的是使用RestTemplate方式调用封装的XID传递的具体逻辑,后台会展开说这个rest目录的具体内容,web目录可以理解成是服务内部的XID传递,然后把XID设置到请求头当中传递给另一个微服务,具体的引入Spring Cloud alibaba seata的自动配置类如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.seata.rest.SeataRestTemplateAutoConfiguration,\
com.alibaba.cloud.seata.web.SeataHandlerInterceptorConfiguration,\
com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration,\
com.alibaba.cloud.seata.feign.SeataFeignClientAutoConfiguration,\
com.alibaba.cloud.seata.feign.hystrix.SeataHystrixAutoConfiguration

这些自动配置类,主要是设置不同请求方式的拦截器,拿SeataRestTemplateInterceptor来说

@Configuration
public class SeataRestTemplateAutoConfiguration {

	@Bean
	public SeataRestTemplateInterceptor seataRestTemplateInterceptor() {
		return new SeataRestTemplateInterceptor();
	}

	@Autowired(required = false)
	private Collection<RestTemplate> restTemplates;

	//设置请求的拦截器
	@Autowired
	private SeataRestTemplateInterceptor seataRestTemplateInterceptor;

	@PostConstruct
	public void init() {
		if (this.restTemplates != null) {
			for (RestTemplate restTemplate : restTemplates) {
				List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(
						restTemplate.getInterceptors());
				//请求之前会先执行这个拦截器
				interceptors.add(this.seataRestTemplateInterceptor);
				restTemplate.setInterceptors(interceptors);
			}
		}
	}

}

具体的拦截器代码如下:

public class SeataRestTemplateInterceptor implements ClientHttpRequestInterceptor {
	@Override
	public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
			ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
		HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest);
		//获取当前线程的全局事务会话ID
		String xid = RootContext.getXID();

		if (!StringUtils.isEmpty(xid)) {
			//设置到请求头,传递到下一个服务当中
			requestWrapper.getHeaders().add(RootContext.KEY_XID, xid);
		}
		return clientHttpRequestExecution.execute(requestWrapper, bytes);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值