分布式事务-seata

1 seata简介

1.1 概述

Seata是阿里开源的分布式事务解决方案中间件,对业务侵入小,在应用中Seata整体事务逻辑基于两阶段提交的模型,核心概念包含三个角色:

  • TC-事务协调者,维护全局和分支事务状态,驱动全局事务提交或回滚
  • TM-事务管理器,定义全局事务的范围,提交或回滚全局事务
  • RM-资源管理器,管理分支事务处理的资源,与TC交谈已注册事务报告和分支事务处理状态,并驱动分支事务提交或回滚

1.2 三种模式

1.2.1 AT 模式

AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

1.2.2 TCC 模式

TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。

1.2.3 Saga 模式

在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。

分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。

1.3 处理过程

分布式事务-seata_第1张图片

  1. TM向TC申请开启一个全局事务,全局事务创建成功并声称一个全局唯一的XID
  2. XID在微服务调用链路中上下文传播
  3. RM向TC注册事务,将其纳入XID管辖
  4. TM向TC发起针对XID的全局提交或回滚协议
  5. TC调度XID下管辖的全部分支事务完成提交或回滚请求

2 入门案例

2.1 seata安装配置

2.1.1 修改配置文件

  • 修改file.conf
    修改的地方用todo标注
## transaction log store, only used in seata-server
store {
  ## store mode: file、db
  // todo
  mode = "db"
  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "mysql"
    password = "mysql"
    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }
}

service {
  # todo 交易服务组映射
  #transaction service group mapping
  vgroupMapping.my_test_tx_group = "cloud_tx_group"
  #only support when registry.type=file, please don't set multiple addresses
  #这里设置ip, 一般不用改
  default.grouplist = "127.0.0.1:8091"
  #disable seata
  disableGlobalTransaction = false
}
  • 修改registry.conf文件
    这里配置nacos注册中心
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "localhost:8848"
    namespace = ""
    group = "DEFAULT_GROUP"
    username = "nacos"
    password = "nacos"
  }
}

2.1.2 初始化数据库

新建seata数据库,根据server-db.conf文件初始化表
在各个客户端根据client-db.conf建表,结构如下
分布式事务-seata_第2张图片

2.2 工程搭建

2.2.1 父工程依赖管理

            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.1</version>
            </dependency>

            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-spring-boot-starter</artifactId>
                <version>1.2.0</version>
                <exclusions>
                    <exclusion>
                        <groupId>io.seata</groupId>
                        <artifactId>seata-all</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
                <version>1.2.0</version>
            </dependency>

2.2.2 seata-account

  • maven.xml
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 监控管理模块 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
  • 配置文件
server:
  port: 9101

spring:
  application:
    name: seata-account

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_account?allowMultiQueries=true&characterEncoding=utf8&useUnicode=true
    username: root
    password: hwl123
    druid:
      initial-size: 10
      min-idle: 10
      max-active: 100
      max-wait: 60000 # 配置获取连接等待超时的时间
      time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      min-evictable-idle-time-millis: 30000 # 配置一个连接在池中最小生存的时间,单位是毫秒

  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    alibaba:
      seata:
        tx-service-group: cloud_tx_group # 这里和file.conf中的 vgroupMapping.my_test_tx_group 保持一致

management:
  endpoints:
    web:
      exposure:
        include: "*"
  • 工程下的file.conf
## transaction log store, only used in seata-server
store {
 ## store mode: file、db
 mode = "db"

 db {
   ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
   datasource = "druid"
   ## mysql/oracle/postgresql/h2/oceanbase etc.
   dbType = "mysql"
   driverClassName = "com.mysql.jdbc.Driver"
   url = "jdbc:mysql://127.0.0.1:3306/seata"
   user = "mysql"
   password = "mysql"
   minConn = 5
   maxConn = 30
   globalTable = "global_table"
   branchTable = "branch_table"
   lockTable = "lock_table"
   queryLimit = 100
   maxWait = 5000
 }
}

service {
 # todo # 这里和file.conf中的 vgroupMapping.my_test_tx_group 保持一致
 vgroupMapping.cloud_tx_group = "default"
 #only support when registry.type=file, please don't set multiple addresses
 #这里设置ip, 一般不用改
 default.grouplist = "127.0.0.1:8091"
 #disable seata
 disableGlobalTransaction = false
}
  • controller
@RestController
@RequestMapping("/account")
public class AccountController {

    @Resource
    private AccountMapper accountMapper;

    @GetMapping("/decrease")
    public void decrease(Integer num) {
        AccountDO accountDO = accountMapper.selectById(1);
        accountDO.setAmount(accountDO.getAmount() - num);
        if (accountDO.getAmount() < 0) {
            throw new RuntimeException("account less than 0");
        }
        accountMapper.updateById(accountDO);
    }

}

2.2.3 seata-store

  • maven
    这里添加一个feign依赖
<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 配置文件
    file.conf 和 application.yml同上
  • java代码
@Slf4j
@Component
public class OrderManager {

    @Resource
    private StoreMapper storeMapper;

    @Resource
    private AccountService accountService;

    @GlobalTransactional(rollbackFor = Exception.class)
    public void order(Integer account, Integer store) {
        StoreDO storeDO = storeMapper.selectById(1);
        storeDO.setGoodNum(storeDO.getGoodNum() - store);
        storeMapper.updateById(storeDO);
        accountService.accountDecrease(account);
        log.info("order end");
    }

}
@RestController
@RequestMapping("/order")
public class OrderController {

    @Resource
    private OrderManager orderManager;

    @GetMapping("/do")
    public void order(Integer account, Integer store) {
        orderManager.order(account, store);
    }

}

测试
分布式事务-seata_第3张图片
账号余额不足,库存也没减,分布式事务成功

3 源码浅析

你可能感兴趣的:(分布式,spring,cloud,seata)