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 可以支持任意的微服务框架。