Seata:分布式事务岂能少了它

Seata是一款开源的分布式事务中间件,旨在解决分布式系统中的数据一致性问题。其核心原理是基于两阶段提交协议和补偿机制,并结合了全局事务ID生成、分支事务管理、数据存储与恢复等关键技术点。下面将详细讲解Seata的分布式事务实现原理。

2023 年 10 月,为了更好的通过社区驱动技术的演进,阿里和蚂蚁集团正式将 Seata 捐赠给 Apache 基金会,该提案已通过了 Apache 基金会的投票决议,Seata 正式进入 Apache 孵化器。

一、Seata技术原理

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

  1. 全局事务ID生成: 在分布式系统中,为了保证事务的唯一性和可追溯性,Seata引入了全局事务ID的概念。全局事务ID由三部分组成:全局事务ID、分支事务ID和服务器ID。其中,全局事务ID是全局唯一的标识,用于标识一个完整的分布式事务。分支事务ID用于标识一个参与者(如数据库、消息队列等)在某个全局事务中的分支事务。服务器ID则用于标识Seata服务器的唯一性。通过全局事务ID的生成,Seata能够对分布式事务进行精确的跟踪和管理。

  2. 分支事务管理: Seata采用了类似于本地事务的方式来管理分支事务。在一个全局事务中,每个参与者都会创建一个分支事务,并将其注册到Seata中进行管理。分支事务的生命周期包括创建、提交和回滚三个阶段。当全局事务发起者发起一个分布式事务时,Seata会为每个参与者创建一个对应的分支事务,并在执行过程中记录其状态。根据不同的业务操作,Seata可以根据全局事务的状态进行相应的处理,保证数据的一致性。

  3. 分布式事务的执行过程: 对于一个全局事务,在Seata中的执行过程可以分为三个阶段:准备阶段、提交阶段和回滚阶段。

  • 3.1 准备阶段: 在准备阶段,Seata会向各个参与者发送prepare请求,要求其进行事务资源的预留。参与者收到prepare请求后,会对事务进行检查,包括对资源的加锁和冲突检测等。如果所有参与者都成功预留了资源并返回成功标志,那么全局事务进入下一个阶段。否则,全局事务将进入回滚阶段。

  • 3.2 提交阶段: 在提交阶段,Seata会向各个参与者发送commit请求,要求其提交事务。参与者收到commit请求后,会对事务进行提交操作,包括对资源的释放和提交确认等。如果所有参与者成功提交事务并返回成功标志,那么全局事务就提交成功。否则,全局事务将进入回滚阶段。

  • 3.3 回滚阶段: 在回滚阶段,Seata会向各个参与者发送rollback请求,要求其回滚事务。参与者收到rollback请求后,会对事务进行回滚操作,包括对资源的释放和回滚恢复等。如果所有参与者成功回滚事务并返回成功标志,那么全局事务就回滚成功。否则,全局事务的状态将保持不变。

  1. 数据存储与恢复机制: 为了保证分布式事务的可靠性和恢复能力,Seata采用了日志存储和恢复机制。在事务执行的过程中,Seata会记录每个分支事务的操作日志,并将其存储在持久化的存储介质中(如数据库)。当系统发生故障或重启时,Seata可以根据事务日志进行事务状态的恢复和重放,从而确保分布式事务的一致性。

我们可以看到Seata是如何实现分布式事务的。它通过全局事务ID生成、分支事务管理、两阶段提交协议、数据存储与恢复等关键技术点,保证了分布式系统中的数据一致性。同时,Seata还具备高可用性和容错机制,能够应对各种故障和异常情况。通过使用Seata,我们可以简化分布式事务的开发和管理,提高系统的可靠性和性能。

二、Seata四大模式

1、Seata AT 模式

AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。

Seata(Simple Extensible Autonomous Transaction Architecture)是一款分布式事务解决方案,它为分布式系统提供了强一致性的事务支持。Seata AT(Auto Commit)模式是Seata支持的一种事务模式,它采用的是自动提交的方式来实现分布式事务。

在Seata AT模式下,应用程序不需要显式地编写事务的开始和结束语句,而是通过Seata框架自动管理事务的提交和回滚。具体流程如下:

  1. 客户端发起一个分布式事务,向Seata注册全局事务。

  2. 客户端执行业务操作,在每个参与者(例如数据库)上生成本地事务。

  3. 客户端向Seata报告本地事务,Seata根据报告的事务状态构建全局事务日志。

  4. 当所有的本地事务都成功执行时,客户端向Seata发送提交全局事务的请求。

  5. Seata协调器收到提交请求后,向各个参与者发送提交事务的指令。

  6. 参与者接收到提交事务的指令后,将本地事务进行提交。

  7. 如果有任何一个参与者的本地事务失败,Seata协调器会发送回滚事务的指令,使得所有的参与者进行事务回滚。

写一个简单的使用Seata AT模式的demo:

java 复制代码
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @GlobalTransactional(rollbackFor = Exception.class)
    public void transfer(String fromAccount, String toAccount, int amount) {
        try {
            // 执行本地事务,转出账户扣减金额
            jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE account_id = ?", amount, fromAccount);

            // 执行本地事务,转入账户增加金额
            jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE account_id = ?", amount, toAccount);
        } catch (Exception e) {
            throw new RuntimeException("Transfer failed");
        }
    }
}

在上述代码中,transfer() 方法被标记为 @GlobalTransactional,表示这是一个全局事务。当该方法被调用时,Seata会自动管理事务提交和回滚。如果任意一个本地事务执行失败,Seata会回滚所有的参与者的事务。

需要注意的是,Seata AT模式需要依赖Seata Server作为协调器来管理全局事务,同时需要对数据库等资源进行适配以支持分布式事务

2、Seata TCC 模式

TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。

Seata TCC(Try-Confirm-Cancel)模式是Seata分布式事务解决方案支持的一种更为灵活的事务模式,它适用于那些无法在执行业务操作之前预知需要在分布式事务中执行哪些操作的场景。TCC模式的基本思想是将一个复杂的业务操作拆分成三个阶段:尝试阶段(Try)、确认阶段(Confirm)和取消阶段(Cancel),并在这三个阶段中通过程序来对业务进行控制。

在Seata TCC模式下,应用程序需要根据业务需求,手动编写每个阶段的代码,并在每个阶段中调用相应的Seata API来协助进行全局事务管理。具体流程如下:

  1. 尝试阶段(Try):在该阶段中,应用程序尝试着对资源进行占用或者锁定,以确保该资源不会被其他参与者修改或删除。如果尝试成功,则将当前状态保存在本地,并向Seata注册全局事务。

  2. 确认阶段(Confirm):在该阶段中,应用程序将确认之前尝试所做的操作,并将其提交到Seata全局事务日志中。

  3. 取消阶段(Cancel):在该阶段中,应用程序将回滚之前的尝试,并且将这些操作的回滚存储到Seata全局事务日志中。

TCC 模式,不依赖于底层数据资源的事务支持,下面写个使用示例:

java 复制代码
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @GlobalTransactional(rollbackFor = Exception.class)
    public void transfer(String fromAccount, String toAccount, int amount) {
        try {
            // 尝试阶段,扣减转出账户金额
            prepareTransfer(fromAccount, amount);

            // 确认阶段,转入账户增加金额
            confirmTransfer(toAccount, amount);
        } catch (Exception e) {
            // 取消阶段,恢复转出账户余额
            cancelTransfer(fromAccount, amount);
            throw new RuntimeException("Transfer failed");
        }
    }

    @Transactional
    public void prepareTransfer(String fromAccount, int amount) throws Exception {
        int balance = jdbcTemplate.queryForObject("SELECT balance FROM account WHERE account_id = ?", Integer.class, fromAccount);
        if (balance < amount) {
            throw new RuntimeException("Insufficient balance");
        }
        jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE account_id = ?", amount, fromAccount);
    }

    @Transactional
    public void confirmTransfer(String toAccount, int amount) {
        jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE account_id = ?", amount, toAccount);
    }

    @Transactional
    public void cancelTransfer(String fromAccount, int amount) {
        jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE account_id = ?", amount, fromAccount);
    }

    @io.seata.rm.tcc.api.TwoPhaseBusinessAction(name = "transferTccService", commitMethod = "confirmTransfer", rollbackMethod = "cancelTransfer")
    public boolean transferPrepare(BusinessActionContext context, String fromAccount, String toAccount, int amount) {
        try {
            prepareTransfer(fromAccount, amount);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }
}

transfer() 方法被标记为 @GlobalTransactional,表示这是一个全局事务。在该方法中,我们手动编写了尝试、确认和取消三个阶段的代码,并在 transferPrepare() 方法中注册了全局事务,并在相应的阶段中调用了Seata提供的API来协助进行全局事务管理。

3、SEATA Saga 模式

Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

Seata Saga模式是Seata分布式事务解决方案支持的一种补偿型分布式事务模式,它适用于那些无法在执行业务操作之前预知需要在分布式事务中执行哪些操作的场景。Saga模式的基本思想是将一个复杂的业务操作拆分成一系列的局部事务,并通过补偿操作来保证全局事务的一致性。

在Seata Saga模式下,应用程序需要根据业务需求,手动编写每个局部事务的代码,并在每个局部事务中调用相应的Seata API来协助进行全局事务管理。具体流程如下:

  1. 发起阶段(Request):在该阶段中,应用程序发送一个全局事务请求,开始执行一系列的局部事务。这些局部事务可以是同步执行的,也可以是异步执行的。

  2. 执行阶段(Local Transaction):在该阶段中,应用程序执行各个局部事务。每个局部事务都需要实现正向操作(Forward)和补偿操作(Compensation)。正向操作用于执行实际的业务操作,而补偿操作用于撤销或回滚正向操作。Seata Saga模式通过记录每个局部事务的执行状态来保证全局事务的一致性。

  3. 补偿阶段(Compensation):如果在执行阶段中的任何一个局部事务失败,将会触发相应的补偿操作来撤销已经执行过的正向操作,以保证全局事务的一致性。

目前SEATA提供的Saga模式是基于状态机引擎来实现的,机制是:

  1. 通过状态图来定义服务调用的流程并生成 json 状态语言定义文件

  2. 状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点

  3. 状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚

    注意: 异常发生时是否进行补偿也可由用户自定义决定

  4. 可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能

写一个使用示例一起学习一下吧:

java 复制代码
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @GlobalTransactional(rollbackFor = Exception.class)
    public void createOrder(String orderId, String customerId, String productId, int quantity) {
        try {
            // 发起阶段,创建订单
            createOrder(orderId, customerId);

            // 执行阶段,扣减库存
            reduceStock(productId, quantity);
        } catch (Exception e) {
            // 补偿阶段,回滚创建的订单
            cancelOrder(orderId);
            throw new RuntimeException("Create order failed");
        }
    }

    @Transactional
    public void createOrder(String orderId, String customerId) {
        jdbcTemplate.update("INSERT INTO orders (order_id, customer_id) VALUES (?, ?)", orderId, customerId);
    }

    @Transactional
    public void reduceStock(String productId, int quantity) {
        int stock = jdbcTemplate.queryForObject("SELECT stock FROM products WHERE product_id = ?", Integer.class, productId);
        if (stock < quantity) {
            throw new RuntimeException("Insufficient stock");
        }
        jdbcTemplate.update("UPDATE products SET stock = stock - ? WHERE product_id = ?", quantity, productId);
    }

    @Transactional
    public void cancelOrder(String orderId) {
        jdbcTemplate.update("DELETE FROM orders WHERE order_id = ?", orderId);
    }

    @io.seata.rm.saga.api.SagaStart
    public boolean createOrderSaga(BusinessActionContext context, String orderId, String customerId, String productId, int quantity) {
        try {
            createOrder(orderId, customerId);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }

    @io.seata.rm.saga.api.SagaEnd
    public boolean reduceStockSaga(BusinessActionContext context, String productId, int quantity) {
        try {
            reduceStock(productId, quantity);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }

    @io.seata.rm.saga.api.SagaCompensate
    public boolean cancelOrderSaga(BusinessActionContext context, String orderId) {
        try {
            cancelOrder(orderId);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }
}

在以上示例中,createOrder() 方法被标记为 @GlobalTransactional,表示这是一个全局事务。在该方法中,我们手动编写了发起、执行和补偿三个阶段的代码,并在相应的阶段中调用了Seata提供的API来协助进行全局事务管理。

4、Seata XA 模式

Seata XA 模式是 Seata 分布式事务解决方案支持的一种两阶段提交分布式事务模式,它适用于那些需要保证 ACID 特性的分布式事务场景。XA 模式的基本思想是通过协调器来协调所有参与者的动作,最终保证整个分布式事务的原子性和一致性。

在 Seata XA 模式下,应用程序需要使用 XA 协议来管理分布式事务,将所有涉及到的资源(如数据库、消息队列等)都注册到全局事务中,并在执行完所有操作后,通过一个协调器来协调所有参与者的提交或回滚操作。具体流程如下:

  1. 发起阶段(Request):在该阶段中,应用程序发送一个全局事务请求,开始执行一系列的局部事务,并将所有涉及到的资源都注册到全局事务中。

  2. 准备阶段(Prepare):在该阶段中,所有参与者将会执行局部事务,并将操作结果保存在本地的事务日志中,同时向全局协调器发送准备请求。如果所有参与者都成功执行了局部事务,并且向协调器发送了准备请求,那么全局协调器将会向每个参与者发送提交请求,否则将会向每个参与者发送回滚请求。

  3. 提交阶段(Commit):在该阶段中,所有参与者将会执行本地的事务提交操作。如果所有参与者的提交操作都成功,那么全局协调器将会向应用程序返回一个提交成功的响应。

  4. 回滚阶段(Rollback):在该阶段中,所有参与者将会执行本地的事务回滚操作。如果任何一个参与者的回滚操作失败,那么全局协调器将会向应用程序返回一个回滚失败的响应。

以下是一个使用 Seata XA 模式的代码案例,方便了解使用:

java 复制代码
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private DataSourceProxy dataSourceProxy;

    @GlobalTransactional(rollbackFor = Exception.class)
    public void createOrder(String orderId, String customerId, String productId, int quantity) {
        try {
            // 发起阶段,创建订单
            createOrder(orderId, customerId);

            // 执行阶段,扣减库存
            reduceStock(productId, quantity);
        } catch (Exception e) {
            throw new RuntimeException("Create order failed");
        }
    }

    public void createOrder(String orderId, String customerId) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceProxy);
        jdbcTemplate.update("INSERT INTO orders (order_id, customer_id) VALUES (?, ?)", orderId, customerId);
    }

    public void reduceStock(String productId, int quantity) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceProxy);
        int stock = jdbcTemplate.queryForObject("SELECT stock FROM products WHERE product_id = ?", Integer.class, productId);
        if (stock < quantity) {
            throw new RuntimeException("Insufficient stock");
        }
        jdbcTemplate.update("UPDATE products SET stock = stock - ? WHERE product_id = ?", quantity, productId);
    }
}

在以上示例中,我们使用了 Seata 提供的 DataSourceProxy 来代理数据库连接池,在 createOrder()reduceStock() 方法中,我们使用了 JdbcTemplate 来操作数据库。在 createOrder() 方法中,我们手动编写了发起和执行两个阶段的代码,并在 @GlobalTransactional 注解中设置了 rollbackFor = Exception.class,表示如果发生任何异常,都将触发全局事务的回滚。

三、总结一下

哎呀,Seata 这个家伙,真是让人又爱又恨啊!

首先,我们来说说它的好处。Seata 是一款分布式事务解决方案,可以帮助我们在分布式系统中实现 ACID 事务特性。这意味着我们可以在分布式环境下,像单机应用一样轻松地管理事务,避免了我们之前那种在分布式场景下手忙脚乱搞事务的尴尬场面。

不过,Seata 的配置和使用过程,也是有点麻烦的。首先,我们需要在每个参与分布式事务的应用中添加 Seata 的依赖,并修改配置文件;其次,我们还需要对数据库等资源进行适配以支持分布式事务;最后,我们还需要编写一些额外的代码来管理分布式事务的整个生命周期。

如果你对 Seata 不了解,可能就会想:"这么麻烦,我还是不用了吧!"但是,如果你深入了解了 Seata,你就会发现,它的确是一个非常强大且实用的工具。在分布式系统中,我们总是需要保证数据的一致性,而 Seata 就是为此而生的。

所以,如果你想要在分布式系统中实现事务控制,Seata 绝对是一个值得学习的工具。当然,学习过程可能会比较痛苦,但是只要坚持下去,最后收获一定是超出预期的!

相关推荐
用户7785371836962 分钟前
一力破万法:从0实现一个http代理池
后端·爬虫
拖孩28 分钟前
微信群太多,管理麻烦?那试试接入AI助手吧~
前端·后端·微信
Humbunklung38 分钟前
Rust枚举:让数据类型告别单调乏味
开发语言·后端·rust
radient1 小时前
Golang-GMP 万字洗髓经
后端·架构
蓝倾1 小时前
如何使用API接口实现淘宝商品上下架监控?
前端·后端·api
舂春儿1 小时前
如何快速统计项目代码行数
前端·后端
Pedantic1 小时前
我们什么时候应该使用协议继承?——Swift 协议继承的应用与思
前端·后端
Codebee1 小时前
如何利用OneCode注解驱动,快速训练一个私有的AI代码助手
前端·后端·面试
martinzh1 小时前
用Spring AI搭建本地RAG系统:让AI成为你的私人文档助手
后端
MMJC61 小时前
Playwright MCP Batch:革命性的批量自动化工具,让 Web 操作一气呵成
前端·后端·mcp