二、 分布式事务
1. 使用方式与概念
1) 事务上下文
Seata的事务上下文由RootContext来管理。
应用开启一个全局事务后,RootContext会自动绑定该事务的XID,事务结束(提交或回滚完成),RootContext会自动解绑XID。
应用可以通过RootContext的API接口来获取当前运行时的全局事务XID。
应用是否运行在一个全局事务的上下文中,就是通过RootContext是否绑定XID来判定的。
2) 事务传播
Seata全局事务的传播机制就是指事务上下文的传播,根本上,就是XID的应用运行时的传播方式。
• 服务内部的事务传播
默认的,RootContext的实现是基于ThreadLocal的,即XID绑定在当前线程上下文中。
所以服务内部的XID传播通常是天然的通过同一个线程的调用链路串连起来的。默认不做任何处理,事务的上下文就是传播下去的。
如果希望挂起事务上下文,则需要通过RootContext提供的API来实现:
• 跨服务调用的事务传播
通过上述基本原理,我们可以很容易理解:
注:
跨服务调用场景下的事务传播,本质上就是要把XID通过服务调用传递到服务提供方,并绑定到RootContext中去。
只要能做到这点,理论上Seata可以支持任意的微服务框架。
2. 完整示例
完整示例请参见Dubbo给的官方示例:https://github.com/apache/dubbo-samples/tree/master/2-advanced/dubbo-samples-seata
3. 对Dubbo支持的解读
下面,我们通过内置的对Dubbo RPC支持机制的解读,来说明Seata在实现对一个特定微服务框架支持的机制。
对Dubbo的支持,我们利用了Dubbo框架的_org.apache.dubbo.rpc.Filter_机制。
/** * The type Transaction propagation filter. */ @Activate(group = { Constants.PROVIDER, Constants.CONSUMER }, order = 100) public class TransactionPropagationFilter implements Filter { private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationFilter.class); @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { String xid = RootContext.getXID(); // 获取当前事务 XID String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID); // 获取 RPC 调用传递过来的 XID if (LOGGER.isDebugEnabled()) { LOGGER.debug("xid in RootContext[" + xid + "] xid in RpcContext[" + rpcXid + "]"); } boolean bind = false; if (xid != null) { // Consumer:把 XID 置入 RPC 的 attachment 中 RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid); } else { if (rpcXid != null) { // Provider:把 RPC 调用传递来的 XID 绑定到当前运行时 RootContext.bind(rpcXid); bind = true; if (LOGGER.isDebugEnabled()) { LOGGER.debug("bind[" + rpcXid + "] to RootContext"); } } } try { return invoker.invoke(invocation); // 业务方法的调用 } finally { if (bind) { // Provider:调用完成后,对 XID 的清理 String unbindXid = RootContext.unbind(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("unbind[" + unbindXid + "] from RootContext"); } if (!rpcXid.equalsIgnoreCase(unbindXid)) { LOGGER.warn("xid in change during RPC from " + rpcXid + " to " + unbindXid); if (unbindXid != null) { // 调用过程有新的事务上下文开启,则不能清除 RootContext.bind(unbindXid); LOGGER.warn("bind [" + unbindXid + "] back to RootContext"); } } } } } }