阿里分布式事务框架Seata

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

中文官方站点:https://seata.io/zh-cn/
seata github开源地址:https://github.com/seata

本地事务

在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。

数据库事务的四大特性:ACID

A(Atomic):原子性构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成 功部分失败的情况。
C(Consistency):一致性,**在事务执行前后,数据库的一致性约束没有被破坏。**比如:张三向李四转 100元,转账前和转账后的数据是正确状态这叫一致性,如果出现张三转出 100 元,李四账户没有增加 100 元这就出现了数据错误,就没有达到一致性。
I(Isolation):隔离性,**数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事务不能看到其他事务的运行过程的中间状态。**通过配置事务隔离级别可以比避免脏读、重复读问题。
D(Durability):持久性事务完成之后,该事务对数据的更改会持久到数据库,且不会被回滚。

数据库事务在实现时会将一次事务的所有操作全部纳入到一个不可分割的执行单元,该执行单元的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。

分布式事务

随着互联网的快速发展,软件系统由原来的单体应用转变为分布式应用
分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务,
例如用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务。

阿里分布式事务框架Seata案例

1、整体项目结构如下图:
在这里插入图片描述
2、数据库设计
新建两个数据库,分别是 db_order (订单数据库), db_account (账户数据库),db_order 数据库里面新建表 t_order 订单表:
在这里插入图片描述
在这里插入图片描述
3、搭建父级项目 seatatest
导入依赖:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <druid.version>1.1.10</druid.version>
        <spring-cloud.version>2020.0.4</spring-cloud.version>
        <springboot.version>2.5.5</springboot.version>
        <springcloudalibaba.version>2021.1</springcloudalibaba.version>
        <fastjson.version>1.2.35</fastjson.version>
        <commons-lang3.version>3.6</commons-lang3.version>
        <seata-common.version>1.0-SNAPSHOT</seata-common.version>
        <mybatis.version>2.2.0</mybatis.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${springboot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${springcloudalibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>

            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons-lang3.version}</version>
            </dependency>

            <dependency>
                <groupId>com.java1234</groupId>
                <artifactId>seata-common</artifactId>
                <version>${seata-common.version}</version>
            </dependency>

            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

4、seata-common子项目搭建
导入依赖:

<dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <!-- spring boot redis 缓存引入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- lettuce pool 缓存连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

用户账户实体类:

public class Account {

    private Integer id; // 编号

    private Integer userId; // 用户编号

    private Integer balance; // 账户余额

    private String remark; // 备注

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Integer getBalance() {
        return balance;
    }

    public void setBalance(Integer balance) {
        this.balance = balance;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }
}

订单实体类:

public class Order {

    private Integer id; // 编号

    private String orderNo; // 订单号

    private Integer userId; // 用户编号

    private Integer count; // 购买数量

    private Integer amount; // 购买金额

    private String remark; // 备注

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getOrderNo() {
        return orderNo;
    }

    public void setOrderNo(String orderNo) {
        this.orderNo = orderNo;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public Integer getAmount() {
        return amount;
    }

    public void setAmount(Integer amount) {
        this.amount = amount;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }
}

5、seata-order子项目搭建
导入依赖:

   <dependency>
            <groupId>com.java1234</groupId>
            <artifactId>seata-common</artifactId>
        </dependency>

application.yml配置:

server:
  port: 8081
  servlet:
    context-path: /

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db_order?serverTimezone=Asia/Shanghai
    username: root
    password: root
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alibaba:
      seata:
        tx-service-group: my_test_tx_group
  application:
    name: seata-order

mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml

seata:
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
    enable-degrade: false
    disable-global-transaction: false

mapper层

public interface OrderMapper {
    /**
     * 创建订单
     * @param order
     */
    void createOrder(Order order);
}

service层:

public interface OrderService {

    /**
     * 创建订单
     * @param order
     */
    void createOrder(Order order);
}

service实现类:

@Service("orderService")
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Override
    public void createOrder(Order order) {
        orderMapper.createOrder(order);
    }
}

controller层:

@RestController
@RequestMapping("/seata")
public class OrderController {

    @Autowired
    private OrderService orderService;
    /**
     * 创建订单
     * @param order
     * @return
     */
    @PostMapping("/createOrder")
    public boolean createOrder(@RequestBody Order order){
        System.out.println("order:"+order);

        order.setOrderNo(UUID.randomUUID().toString());  // 生成订单ID
        orderService.createOrder(order);
        return true;

    }
}

mapperxml层:

 <select id="createOrder" parameterType="com.java1234.entity.Order" >
        insert into t_order values(null,#{orderNo},#{userId},#{count},#{amount},#{remark})
    </select>

5、seata-account子项目搭建
导入依赖:

    <dependency>
            <groupId>com.java1234</groupId>
            <artifactId>seata-common</artifactId>
        </dependency>

application.yml配置:

server:
  port: 8082
  servlet:
    context-path: /

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db_account?serverTimezone=Asia/Shanghai
    username: root
    password: root
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alibaba:
       seata:
         tx-service-group: my_test_tx_group
  application:
    name: seata-account

mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml

seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: ""
    registry:
      type: nacos
      nacos:
        application: seata-server
        server-addr: 127.0.0.1:8848
        group: "SEATA_GROUP"
        namespace: ""

mapperxml:

    <select id="decrease" parameterType="Map" >
        UPDATE t_account SET balance=balance-#{amount} WHERE userId=#{userId}
    </select>

mapper:

@Mapper
public interface AccountMapper {

    /**
     * 账户扣钱
     */
    void decrease(Map map);
}

service:

public interface AccountService {

    /**
     * 账户扣钱
     */
    void decrease(Map map);
}

service实现类:

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    @Override
    public void decrease(Map map) {
        accountMapper.decrease(map);
    }
}

controller层:

@RestController
@RequestMapping("/account")
public class AccountController {

    @Autowired
    private AccountService accountService;

    /**
     * 给指定用户账户扣钱
     * @param amount
     * @param userId
     * @return
     */
    @PostMapping("/decrease")
    public boolean decrease(@RequestParam("amount")Integer amount, @RequestParam("userId")Integer userId){
        System.out.println("amount:"+amount+",userId:"+userId);

        Map<String,Object> map=new HashMap<>();
        map.put("amount",amount);
        map.put("userId",userId);

        accountService.decrease(map);
        return true;

    }
}

启动类:

@SpringBootApplication
@MapperScan("com.java1234.mapper")
@EnableDiscoveryClient
public class AccountApplication {

    public static void main(String[] args) {
        SpringApplication.run(AccountApplication.class, args);
    }
}

6、seata-web子项目搭建
导入依赖:

    <dependencies>
        <dependency>
            <groupId>com.java1234</groupId>
            <artifactId>seata-common</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
    </dependencies>

application.yml配置类:

server:
  port: 80
  servlet:
    context-path: /


spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alibaba:
      seata:
        tx-service-group: my_test_tx_group
  application:
    name: seata-web


seata:
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
    enable-degrade: false
    disable-global-transaction: false

controller层:

@RestController
public class WebController {

    @Autowired
    private OrderFeignService orderFeignService;

    @Autowired
    private AccountFeignService  accountFeignService;

    /**
     * 下单 1,创建订单 2,账户扣钱
     * @param order
     * @return
     */
    @PostMapping("/shopping")
    @GlobalTransactional
    public boolean shopping(Order order){
        orderFeignService.createOrder(order); // 创建订单
        accountFeignService.decrease(order.getAmount(),order.getUserId()); // 账户扣钱
        return true;
    }
}

feign层:

@FeignClient("seata-account")
public interface AccountFeignService {

    /**
     * 账号扣钱
     * @param amount
     * @param userId
     * @return
     */
    @PostMapping("/account/decrease")
    public boolean decrease(@RequestParam("amount")Integer amount, @RequestParam("userId")Integer userId);

}
@FeignClient("seata-order")
public interface OrderFeignService {

    /**
     * 创建订单
     * @param order
     * @return
     */
    @PostMapping("/seata/createOrder")
    public boolean createOrder(@RequestBody Order order);

}

7、项目启动:
先启动nacos服务注册中心
在这里插入图片描述
测试接口:http://localhost/shopping
postman测试:
在这里插入图片描述
模拟抛出异常时:
在这里插入图片描述
此时数据库中:生成订单,且相应账户余额增加及减少
在这里插入图片描述
在这里插入图片描述
8、CAP理论
CAP理论:一个分布式系统不可能同时满足一致性,可用性和分区容错性这个三个基本需求,最多只能
同时满足其中两项

一致性©:数据在多个副本之间是否能够保持一致的特性。
可用性(A):是指系统提供的服务必须一致处于可用状态,对于每一个用户的请求总是在有限的时间内返 回结果,超过时间就认为系统是不可用的
分区容错性§:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和 可用性的服务,除非整个网络环境都发生故障。

CAP定理的应用

放弃P(CA):如果希望能够避免系统出现分区容错性问题,一种较为简单的做法就是将所有的数据(或者 是与事物先相关的数据)都放在一个分布式节点上,这样虽然无法保证100%系统不会出错,但至少不会 碰到由于网络分区带来的负面影响
放弃A(CP):其做法是一旦系统遇到网络分区或其他故障时,那受到影响的服务需要等待一定的时间,应 用等待期间系统无法对外提供正常的服务,即不可用
**放弃C(AP)😗*这里说的放弃一致性,并不是完全不需要数据一致性,是指放弃数据的强一致性,保留数据 的最终一致性。

BASE理论

BASE是基本可用,软状态,最终一致性。是对CAP中一致性和可用性权限的结果,是基于CAP定理演化
而来的,核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特定,采用适当的方式 来使系统达到最终一致性

2PC提交
二阶段提交协议是将事务的提交过程分成提交事务请求和执行事务提交两个阶段进行处理。
阶段1:提交事务请求

事务询问:协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各 参与者的响应
执行事务:各参与者节点执行事务操作,并将Undo和Redo信息记入事务日志中 如果参与者成功执事务操作,就反馈给协调者Yes响应,表示事物可以执行,如果没有成功执行事
务,就反馈给协调者No响应,表示事务不可以执行二阶段提交一些的阶段一夜被称为投票阶段,即各参与者投票票表明是否可以继续执行接下去的事务提交操作

阶段二:执行事务提交

  1. 假如协调者从所有的参与者或得反馈都是Yes响应,那么就会执行事务提交。
  2. 发送提交请求:协调者向所有参与者节点发出Commit请求
  3. 事务提交:参与者接受到Commit请求后,会正式执行事务提交操作,并在完成提交之后放弃整个 事务执行期间占用的事务资源
  4. 反馈事务提交结果:参与者在完成事物提交之后,向协调者发送ACK消息
  5. 完成事务:协调者接收到所有参与者反馈的ACK消息后,完成事务

中断事务

假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参 与者的反馈响应,那么就中断事务。
发送回滚请求:协调者向所有参与者节点发出Rollback请求
事务回滚:参与者接收到Rollback请求后,会利用其在阶段一种记录的Undo信息执行事物回滚操 作,并在完成回滚之后释放事务执行期间占用的资源。 反馈事务回滚结果:参与则在完成事务回滚之后,向协调者发送ACK消息
中断事务:协调者接收到所有参与者反馈的ACk消息后,完成事务中断

优缺点

原理简单,实现方便 缺点是同步阻塞,单点问题,脑裂,保守

3PC提交

三阶段提,也叫三阶段提交协议,是二阶段提交(2PC)的改进版本。 与两阶段提交不同的是,三阶段提交有两个改动点。引入超时机制。同时在协调者和参与者中都引
入超时机制。在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点 的状态是一致的。
三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

TCC分布式事务

TCC是服务化的两阶段编程模型,其Try、Confirm、Cancel,3个方法均由业务编码实现
TCC要求每个分支事务实现三个操作:预处理Try,确认Confirm,撤销Cancel。
Try操作做业务检查及资源预留,Confirm做业务确认操作。 Cancel实现一个与Try相反的操作即回滚操作。
TM首先发起所有的分支事务Try操作,任何一个分支事务的Try操作执行失败,TM将会发起所有分
支事务的Cancel操作,若Try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中
Confirm/Cancel操作若执行失败,TM会进行重试。

TCC的三个阶段

Try阶段是做业务检查(一致性)及资源预留(隔离),此阶段仅是一个初步操作,它和后续的Confirmy一 起才能构成一个完整的业务逻辑
Confirm阶段是做确认提交,Try阶段所有分支事务执行成功后开始执行Confirm,通常情况下,采 用TCC则认为Confirm阶段是不会出错的,即:只要Try成功,Confirm一定成功,若Confirm阶段真
的出错,需要引入重试机制或人工处理
**Cancel阶段是在业务执行错误需要回滚到状态下执行分支事务的取消,预留资源的释放,**通常情况 下,采用TCC则认为Cancel阶段也一定是真功的,若Cance阶段真的出错,需要引入重试机制或人工 处理
TM事务管理器:TM事务管理器可以实现为独立的服务,也可以让全局事务发起方充当TM的角 色,TM独立出来是为了公用组件,是为了考虑系统结构和软件的复用
TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条,用来记录事
务上下文,追踪和记录状态,用于Confirm和cacel失败需要进行重试,因此需要实现幂等

TCC的三种异常处理情况

幂等处理
因为网络抖动等原因,分布式事务框架可能会重复调用同一个分布式事务中的一个分支事务的二阶 段接口。所以分支事务的二阶段接口Confirm/Cancel需要能够保证幂等性。如果二阶段接口不能保
证幂等性,则会产生严重的问题,造成资源的重复使用或者重复释放,进而导致业务故障。
对于幂等类型的问题,通常的手段是引入幂等字段进行防重放攻击。对于分布式事务框架中的幂等 问题,同样可以祭出这一利器。
幂等记录的插入时机是参与者的Try方法,此时的分支事务状态会被初始化为INIT。然后当二阶段
的Confirm/Cancel执行时会将其状态置为CONFIRMED/ROLLBACKED。
当TC重复调用二阶段接口时,参与者会先获取事务状态控制表的对应记录查看其事务状态。如果状
态已经为CONFIRMED/ROLLBACKED,那么表示参与者已经处理完其分内之事,不需要再次执
行,可以直接返回幂等成功的结果给TC,帮助其推进分布式事务。

空回滚
当没有调用参与方Try方法的情况下,就调用了二阶段的Cancel方法,Cancel方法需要有办法识别 出此时Try有没有执行。如果Try还没执行,表示这个Cancel操作是无效的,即本次Cancel属于空回
滚;如果Try已经执行,那么执行的是正常的回滚逻辑。
要应对空回滚的问题,就需要让参与者在二阶段的Cancel方法中有办法识别到一阶段的Try是否已
经执行。很显然,可以继续利用事务状态控制表来实现这个功能。
当Try方法被成功执行后,会插入一条记录,标识该分支事务处于INIT状态。所以后续当二阶段的
Cancel方法被调用时,可以通过查询控制表的对应记录进行判断。如果记录存在且状态为INIT,就
表示一阶段已成功执行,可以正常执行回滚操作,释放预留的资源;如果记录不存在则表示一阶段 未执行,本次为空回滚,不释放任何资源。

资源悬挂
问题:TC回滚事务调用二阶段完成空回滚后,一阶段执行成功
解决:事务状态控制记录作为控制手段,二阶段发现无记录时插入记录,一阶段执行时检查记录是 否存在

TCC和2PC比较

2PC通常都是在跨库的DB层面,而TCC则在应用层面处理,需要通过业务逻辑实现,这种分布式事
务的实现方式优势在于,可以让应用自己定义数据操作的粒度,使得降低锁冲突,提高吞吐量成为 可能
不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现Try,confirm,cancel
个操作。此外,其实现难度也比较大,需要按照网络状态,系统故障的不同失败原因实现不同的回滚策略

消息队列实现可靠消息最终一致性

可靠消息最终一致性就是保证消息从生产方经过消息中间件传递到消费方的一致性
RocketMQ主要解决了两个功能:本地事务与消息发送的原子性问题。事务参与方接收消息的可靠 性
可靠消息最终一致性事务适合执行周期长且实时性要求不高的场景,引入消息机制后,同步的事务
操作变为基于消息执行的异步操作,避免分布式事务中的同步阻塞操作的影响,并实现了两个服务 的解耦

seata原理详解

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

Seata术语

TC (Transaction Coordinator) - 事务协调者 维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM(Transaction Manager) - 事务管理器 定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (ResourceManager) - 资源管理器 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚

Seata管理分布式事务的典型生命周期
TM要求TC开始新的全局事务。 TC生成表示全局事务的XID。
XID通过微服务的调用链传播。
RM将本地事务注册为XID到TC的相应全局事务的分支。
TM要求TC提交或回滚XID的相应全局事务。
TC在XID的相应全局事务下驱动所有分支事务,以完成分支提交或回滚。

分布式事务是由一批分支事务组成的全局事务,通常分支事务就是本地事务。

AT 模式
前提

基于支持本地 ACID 事务的关系型数据库。 Java 应用,通过 JDBC 访问数据库。

整体机制

两阶段提交协议的演变:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段: 提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。

写隔离

一阶段本地事务提交前,需要确保先拿到 全局锁 。
拿不到 全局锁 ,不能提交本地事务。
拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

读隔离

在数据库本地事务隔离级别
读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是读未提交(Read Uncommitted) 。
如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过SELECT FOR UPDATE 语句的代理。SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚
SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿
到,即读取的相关数据是已提的,才返回。
出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR
UPDATE 的 SELECT 语句。

在这里插入图片描述
一阶段

过程:

  1. 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = ‘TXC’)等相关 的信息。
  2. 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。

二阶段-回滚

  1. 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
  2. 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
  3. 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务 之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
  4. 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:update product set name = ‘TXC’ where id = 1;

二阶段-提交

  1. 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
  2. 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

TCC 模式
回顾总览中的描述:一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:

一阶段 prepare 行为
二阶段 commit 或 rollback 行为
在这里插入图片描述
根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction Mode 和 Manual (Branch) Transaction Mode.

AT 模式(参考链接 TBD)
基于 支持本地 ACID 事务 的 关系型数据库:

一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。
二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。
相应的,TCC 模式,不依赖于底层数据资源的事务支持:

一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
二阶段 commit 行为:调用 自定义 的 commit 逻辑。
二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

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

Saga模式示意图
在这里插入图片描述

理论基础:Hector & Kenneth 发表论⽂ Sagas (1987)

适用场景:
业务流程长、业务流程多
参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
优势:
一阶段提交本地事务,无锁,高性能
事件驱动架构,参与者可异步执行,高吞吐
补偿服务易于实现
缺点:
不保证隔离性(应对方案见用户文档)

seata事务分组介绍
事务分组是什么?
**事务分组是seata的资源逻辑,类似于服务实例。**在file.conf中的my_test_tx_group就是一个事务分组。
通过事务分组如何找到后端集群?

  1. 首先程序中配置了事务分组(GlobalTransactionScanner 构造方法的txServiceGroup参数)
  2. 程序会通过用户配置的配置中心去寻找service.vgroupMapping .[事务分组配置项],取得配置项的 值就是TC集群的名称
  3. 拿到集群名称程序通过一定的前后缀+集群名称去构造服务名,各配置中心的服务名实现不同
  4. 拿到服务名去相应的注册中心去拉取相应服务名的服务列表,获得后端真实的TC服务列表 为什么这么设计,不直接取服务名? 这里多了一层获取事务分组到映射集群的配置。这样设计后,事务分组可以作为资源的逻辑隔离单位,
    出现某集群故障时可以快速failover,只切换对应分组,可以把故障缩减到服务级别,但前提也是你有足 够server集群
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值