分布式事务解决方案之seata(AT模式)

文章目录

  • AT模式说明
  • demo前置工作
  • demo搭建
  • 验证demo
  • demo代码下载

Seata 是一款阿里巴巴开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。 seata官网

应对不同场景,seata提供了AT、TCC、SAGA和XA事务模式,本文采用AT模式来简单演示seata的demo。

AT模式说明

AT模式基于支持本地ACID事务的关系型数据库,如Mysql。AT模式中最关键的就是全局锁,一阶段事务提交的时候要先获取全局锁,提交完就释放全局锁,二阶段需要等待全局锁的释放,获取到全局锁后才能做提交操作。当发生异常需要回滚的时候,会通过回滚日志进行反向补偿,这个回滚日志会存储在undo_log表中,所以AT模式需要先创建undo_log表。

demo前置工作

组件/框架 版本
seata 1.3.0
mysql 5.8
zookeeper 3.16
springboot 2.2.5.RELEASE
  • 建表和初始化数据

    # AT模式必须要的日志表
    CREATE TABLE `undo_log` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `branch_id` bigint(20) NOT NULL,
      `xid` varchar(100) NOT NULL,
      `context` varchar(128) NOT NULL,
      `rollback_info` longblob NOT NULL,
      `log_status` int(11) NOT NULL,
      `log_created` datetime NOT NULL,
      `log_modified` datetime NOT NULL,
      `ext` varchar(100) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
    # 业务数据表
    CREATE TABLE `sys_user` (
      `id` varchar(36) NOT NULL,
      `name` varchar(100) NOT NULL,
      `msg` varchar(500) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    INSERT INTO `sys_user` (`id`, `name`, `msg`) VALUES ('1', '小王', '初始化数据');
    
    
    
  • 从seata官网下载seata-server

    解压后修改registry.conf文件,registry.conf是修改注册中心配置的,这里采用zk作为注册中心。(file.conf是修改事务日志存储,这里可以不用改,用file就行)

分布式事务解决方案之seata(AT模式)_第1张图片

  1. 先修改register.conf的配置为zk
    分布式事务解决方案之seata(AT模式)_第2张图片
    分布式事务解决方案之seata(AT模式)_第3张图片

demo搭建

  1. 把seata软件中conf文件夹下的file.conf.example和register.conf复制到项目根目录, 其中file.conf.example 重命名为file.conf。并把里面server端的配置删除,只保留client的配置,这里的配置可以参考官网的配置,同时添加service 配置,否则项目启动会报找不到服务的错误。file.conf 配置如下,注意service 的配置vgroupMapping.tx_group = “default” , tx_group 是事务分组,可以自己命名,default是server端中zk的cluster值, 必须和zk的配置一致

    # file.conf文件
    transport{
      type = "TCP"
      server = "NIO"
      heartbeat = true
      enable-client-batch-send-request = true
      thread-factory {
        boss-thread-prefix = "NettyBoss"
        worker-thread-prefix = "NettyServerNIOWorker"
        server-executor-thread-prefix = "NettyServerBizHandler"
        share-boss-worker = false
        client-selector-thread-prefix = "NettyClientSelector"
        client-selector-thread-size = 1
        client-worker-thread-prefix = "NettyClientWorkerThread"
        boss-thread-size = 1
        worker-thread-size = 8
      }
      shutdown {
        wait = 3
      }
      serialization = "seata"
      compressor = "none"
    }
    service {
      vgroupMapping.tx_group = "default" 
      default.grouplist = "127.0.0.1:8091"
      enableDegrade = false
      disableGlobalTransaction = false
    }
    
    client {
      rm {
        async.commit.buffer.limit = 10000
        lock {
          retry.internal = 10
          retry.times = 30
          retry.policy.branch-rollback-on-conflict = true
        }
        report.retry.count = 5
        table.meta.check.enable = false
        report.success.enable = true
      }
      tm {
        commit.retry.count = 5
        rollback.retry.count = 5
      }
      undo {
        data.validation = true
        log.serialization = "jackson"
        log.table = "undo_log"
      }
      log {
        exceptionRate = 100
      }
      support {
        spring.datasource.autoproxy = false
      }
    }
    
  2. 这里要区分两个概念,seata软件那里的配置是server端的配置,项目里的是client端的配置

  3. 项目引入seata依赖,这里建议用spring-cloud-alibaba-seata包,不要用seata-all, 能减少很多的配置

     
            
                com.alibaba.cloud
                spring-cloud-alibaba-seata
                2.1.0.RELEASE
            
            
            
                io.seata
                seata-discovery-zk
                1.2.0
            
    
  4. yml修改seata配置,其中tx_group 是事务分组名称,需要和第一步的file.conf里面事务分组配置一致

    spring:
      application:
        name: demo-sys2
      cloud:
        zookeeper:
          connect-string: localhost:2181
        alibaba:
          seata:
            tx-service-group: tx_group
    
  5. 增加seata核心配置类SeataConfiguration,这里采用druid作为连接池,所以pom文件要引入druid的依赖。因为引入的是spring-cloud-alibaba-seata依赖,不是seata-all依赖,所以不用加@GlobalTransactionScanner 的bean配置

    
    @Configuration
    public class SeataConfiguration {
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource druidDataSource() {
            DruidDataSource druidDataSource = new DruidDataSource();
            return druidDataSource;
        }
        @Primary
        @Bean("dataSource")
        public DataSourceProxy dataSource(DataSource dataSource) {
            return new DataSourceProxy(dataSource);
        }
    
        @Bean
        @ConfigurationProperties(prefix = "mybatis")
        public MybatisSqlSessionFactoryBean sqlSessionFactoryBean( DataSource dataSource) throws IOException {
            // 这里用 MybatisSqlSessionFactoryBean 代替了 SqlSessionFactoryBean,否则 MyBatisPlus 不会生效
            MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
            mybatisSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                    .getResources("classpath*:/dao/*/*.xml"));
            mybatisSqlSessionFactoryBean.setDataSource(dataSource);
            return mybatisSqlSessionFactoryBean;
        }
    
    }
    
    
  6. TX1的核心业务代码, 加上GlobalTransactional来获取全局锁以及混滚

    /**
         * 修改 用户信息
         *
         * @param sysUser
         * @return
         */
        @GlobalTransactional(timeoutMills = 300000, name = "dubbo-gts-seata-example")
        @Override
        public ServiceResponse update(SysUser sysUser) {
    
    
            sysUserDao.updateById(sysUser);
    
            SysUser sysUserNew = new SysUser();
            sysUserNew.setId("3");
            sysUserNew.setName("钱六");
            sysUserNew.setMsg("新的用户");
            sysUserDao.insert(sysUserNew);
            try {
                // 方便数据库看数据,暂停5秒
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            return ServiceResponse.createSuccess();
    
        }
    
    
    1. TX2的核心业务代码

       /**
           * 修改 用户信息
           *
           * @param sysUser
           * @return
           */
          @GlobalTransactional(timeoutMills = 300000, name = "dubbo-gts-seata-example")
          @Override
          public ServiceResponse update(SysUser sysUser) {
      
      
              sysUserDao.updateById(sysUser);
      
              SysUser sysUserNew = new SysUser();
              sysUserNew.setId("3");
              sysUserNew.setName("钱六");
              sysUserNew.setMsg("新的用户");
              sysUserDao.insert(sysUserNew);
              try {
                  // 方便数据库看数据,暂停5秒
                  Thread.sleep(5 * 1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              return ServiceResponse.createSuccess();
      
          }
      

验证demo

  1. 初始化数据为
    在这里插入图片描述

  2. 分别启动两个微服务,用postman去修改id为1的用户数据,

    分布式事务解决方案之seata(AT模式)_第4张图片

  3. 可以看到,在demo1在一阶段提交后,用户id为1的数据已经改动了

在这里插入图片描述

  1. 然后feign调用demo2的修改用户后,可以看到id为1的数据成功改为王五,同时增加一条 “钱六”的数据
    分布式事务解决方案之seata(AT模式)_第5张图片

  2. demo1异常回滚,可以看到,这时数据又变为初始化的数据了,说明回滚成功,demo演示结束

分布式事务解决方案之seata(AT模式)_第6张图片

  1. undo_log表的变化,在demo1事务提交后,undo_log会插入一条数据(也可能多条,多个sql操作就会多条),回滚或者事务正常的时候,这条数据就会被删除,同时反向补偿也会用到undo_log的数据。如果你手动删除undo_log的数据,就会导致反向补偿异常,也就是数据回滚不完全。最后结果可能就回滚一部分而已,如下图,只回滚到一阶段提交的数据

分布式事务解决方案之seata(AT模式)_第7张图片

分布式事务解决方案之seata(AT模式)_第8张图片

  1. 当又有一条新的线程尝试修改id为1的数据时。因为全局锁一直被之前的线程持有,所以修改会失败
    分布式事务解决方案之seata(AT模式)_第9张图片

demo代码下载

你可能感兴趣的:(分布式)