事务上下文
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 的应用运行时的传播方式。
- 服务内部的事务传播
/**
* 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);
}
}
}
}
- 跨服务调用的事务传播
跨服务调用场景下的事务传播,本质上就是要把 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);
}
}