1SEATA介绍 · SpringCloud微服务实战 · 看云
导航
Seata(Simple Extensible Autonomous Transaction Architecture) 是 阿里巴巴开源的分布式事务中间件,以高效并且对业务 0 侵入的方式,解决微服务场景下面临的分布式事务问题
1. Seata介绍
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案
Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。
2. 资源目录介绍
- client
存放client端sql脚本,参数配置
- config-center
各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件
- server
server端数据库脚本及各个容器配置
3. Seata API
Seata API 分为两大类:High-Level API 和 Low-Level API :
- High-Level API:用于事务边界定义、控制及事务状态查询
- Low-Level API:用于控制事务上下文的传播
3.1 High-Level API
3.1.1 GlobalTransaction
全局事务:包括开启事务、提交、回滚、获取当前状态等方法。
public interface GlobalTransaction {
void begin() throws TransactionException;
void begin(int timeout) throws TransactionException;
void begin(int timeout, String name) throws TransactionException;
void commit() throws TransactionException;
void rollback() throws TransactionException;
GlobalStatus getStatus() throws TransactionException;
String getXid();
}
3.1.2 GlobalTransactionContext
GlobalTransaction 实例的获取需要通过 GlobalTransactionContext:
public static GlobalTransaction getCurrentOrCreate() {
GlobalTransaction tx = getCurrent();
if (tx == null) {
return createNew();
}
return tx;
}
public static GlobalTransaction reload(String xid) throws TransactionException {
GlobalTransaction tx = new DefaultGlobalTransaction(xid, GlobalStatus.UnKnown, GlobalTransactionRole.Launcher) {
@Override
public void begin(int timeout, String name) throws TransactionException {
throw new IllegalStateException("Never BEGIN on a RELOADED GlobalTransaction. ");
}
};
return tx;
}
3.1.3 TransactionalTemplate
事务化模板:通过上述 GlobalTransaction 和 GlobalTransactionContext API 把一个业务服务的调用包装成带有分布式事务支持的服务。
public class TransactionalTemplate {
public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException {
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
try {
tx.begin(business.timeout(), business.name());
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.BeginFailure);
}
Object rs = null;
try {
rs = business.execute();
} catch (Throwable ex) {
try {
tx.rollback();
throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.RollbackFailure, ex);
}
}
try {
tx.commit();
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.CommitFailure);
}
return rs;
}
}
模板方法执行的异常:ExecutionException
class ExecutionException extends Exception {
private GlobalTransaction transaction;
private Code code;
private Throwable originalException;
外层调用逻辑 try-catch 这个异常,根据异常编码进行处理:
- BeginFailure(开启事务失败):getCause() 得到开启事务失败的框架异常,getOriginalException() 为空。
- CommitFailure(全局提交失败):getCause() 得到全局提交失败的框架异常,getOriginalException() 为空。
- RollbackFailure(全局回滚失败):getCause() 得到全局回滚失败的框架异常,getOriginalException() 业务应用的原始异常。
- RollbackDone(全局回滚成功):getCause() 为空,getOriginalException() 业务应用的原始异常。
3.2. Low-Level API
3.2.1 RootContext
事务的根上下文:负责在应用的运行时,维护 XID 。
public static String getXID() {
return CONTEXT_HOLDER.get(KEY_XID);
}
public static void bind(String xid) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("bind " + xid);
}
CONTEXT_HOLDER.put(KEY_XID, xid);
}
public static String unbind() {
String xid = CONTEXT_HOLDER.remove(KEY_XID);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("unbind " + xid);
}
return xid;
}
public static boolean inGlobalTransaction() {
return CONTEXT_HOLDER.get(KEY_XID) != null;
}
High-Level API 的实现都是基于 RootContext 中维护的 XID 来做的。
应用的当前运行的操作是否在一个全局事务的上下文中,就是看 RootContext 中是否有 XID。
RootContext 的默认实现是基于 ThreadLocal 的,即 XID 保存在当前线程上下文中。
Low-Level API 的两个典型的应用场景:
3.2.2 远程调用事务上下文的传播
远程调用前获取当前 XID:
String xid = RootContext.getXID();
远程调用过程把 XID 也传递到服务提供方,在执行服务提供方的业务逻辑前,把 XID 绑定到当前应用的运行时:
RootContext.bind(rpcXid);
3.2.3 事务的暂停和恢复
在一个全局事务中,如果需要某些业务逻辑不在全局事务的管辖范围内,则在调用前,把 XID 解绑:
String unbindXid = RootContext.unbind();
待相关业务逻辑执行完成,再把 XID 绑定回去,即可实现全局事务的恢复:
RootContext.bind(unbindXid);
4. 微服务框架支持
4.1 事务上下文
Seata 的事务上下文由 RootContext 来管理。
应用开启一个全局事务后,RootContext 会自动绑定该事务的 XID,事务结束(提交或回滚完成),RootContext 会自动解绑 XID。
RootContext.bind(xid);
String xid = RootContext.unbind();
应用可以通过 RootContext 的 API 接口来获取当前运行时的全局事务 XID。
String xid = RootContext.getXID();
应用是否运行在一个全局事务的上下文中,就是通过 RootContext 是否绑定 XID 来判定的。
public static boolean inGlobalTransaction() {
return CONTEXT_HOLDER.get(KEY_XID) != null;
}
4.2 事务传播
Seata 全局事务的传播机制就是指事务上下文的传播,根本上,就是 XID 的应用运行时的传播方式。
4.2.1 服务内部的事务传播
默认的,RootContext 的实现是基于ThreadLocal的,即 XID 绑定在当前线程上下文中。
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);
}
}
所以服务内部的 XID 传播通常是天然的通过同一个线程的调用链路串连起来的。默认不做任何处理,事务的上下文就是传播下去的。
如果希望挂起事务上下文,则需要通过 RootContext 提供的 API 来实现:
String xid = RootContext.unbind();
RootContext.bind(xid);
4.2.2 跨服务调用的事务传播
通过上述基本原理,我们可以很容易理解:
跨服务调用场景下的事务传播,本质上就是要把 XID 通过服务调用传递到服务提供方,并绑定到 RootContext 中去。
只要能做到这点,理论上 Seata 可以支持任意的微服务框架。