Seata服务的搭建、Seata AT模式演示

  • Post author:
  • Post category:其他

Seata 快速开始

一、数据库中添加回滚日志表UNDO_LOG:

  1.  UNDO_LOG必须在每个业务数据库中出现,用于保存回滚操作数据。
  2. 全局事务提交时,对应的UNDO_LOG记录直接删除。
  3. 全局事务回滚时,通过该表回滚到以前的数据,并删除UNDO_LOG记录。

Seata的UNDO_LOG表和数据库的UNDO_LOG是相似的,只不过它们的范围不一样

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=1 DEFAULT CHARSET=utf8;

二、搭建Seata的TC 

1.在官网下载seata-server包,解压。下载地址

2.tc服务在管理分布式事务时,需要记录事务相关数据到数据库中,你需要提前创建好这些表,创建一个seata的数据库,新增如下表。

  • global_table:全局事务表
  • branch_table:分支信息表
  • lock_table:加锁的表
-- 全局事务表--
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8;
 
-- 事务分支表 --
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = INNODB DEFAULT CHARSET = utf8;
 
-- 锁定表--
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8;
 
-- seata新版本加的锁表--
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
    `lock_key`       CHAR(20) NOT NULL,
    `lock_value`     VARCHAR(20) NOT NULL,
    `expire`         BIGINT,
    PRIMARY KEY (`lock_key`)
    ) ENGINE = INNODB
    DEFAULT CHARSET = utf8mb4;
 
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

前面的UNDO_LOG表是加在业务数据库中的。每个业务数据库都要有UNDO_LOG

seata1.4.2之后,需要回滚的表日期类型不能使用datetime,可以使用timestamp

3.修改seata服务的配置文件file.conf(可以不做这一步,可以将这些配置放在远程配置中心)

4.修改配置文件registry.conf

这里的namespace和group项一定要和nacos的一致

5. 在registry.conf配置中还可以修改Seata-server的配置中心,默认是以file文件进行存储的,也就是我们上面设置的file.conf,现在我顺便把配置放到nacos上

#config部分
nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = "a971c0d9-771c-4c21-b4f3-3c04c499d831"
    cluster = "default"
    username = "nacos"
    password = "nacos"
}

 6.下载nacos-config脚本和config.txt点击进入下载页,这两个就相当于seata的全部配置,比file.conf更全面。

  • nacos-config.shnacos-config.py选择一个:在seata目录下新建 script 目录,将 nacos-config.sh 放入script 目录下

  • config.txt:该文件存放在将seata目录下,与conf、lib目录同级,seata的非常全的配置内容,可通过nacos-config.sh脚本推送到nacos配置中心

  • 修改config.txt的内容

  •  打开git bashlinux类命令行,执行sh脚本(注意脚本是否有执行的权限),导入config.txt的配置到nacos配置中心里 
# -h 主机,你可以使用localhost,-p 端口号 ,-t 命名空间ID,-u 用户名,-w 密码
sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA-GROUP -t a971c0d9-771c-4c21-b4f3-3c04c499d831 -u nacos -w nacos

seata配置文件非常之多,建议新建一个命名空间单独存放 

 

 7.启动seata-server.bat,并查看nacos服务,若seata服务注册成功,表示注册中心和配置中心成功

三、微服务集成Seata-Server

我们的代码逻辑为:用户购买某件商品,首先查看库存是否充足,如果充足的话,再远程调用订单服务,在数据库中新建一个订单。

扣减库存的service代码如下:

 创建订单的service代码如下:

Seata执行流程(AT模式)

事务执行成功的情况:

 事务执行失败的情况:

 要点说明:

1.每个RM使用DataSourceProxy连接数据库,其目的是使用ConnectionProxy,使用数据源和数据连接代理的目的就是在第一阶段将UNDO_LOG和业务数据放在一个本地事务提交,这样就保存了只要有业务操作就一定有UNDO_LOG。

2.在第一阶段UNDO_LOG中存放了数据修改前和修改后的值,为事务回滚作好准备,所以第一阶段完成就已经将分支事务提交,也就释放了锁资源

3.TM开启全局事务开始,将XID全局事务id放在事务上下文中,通过feign调用也将XID传入下游分支事务,每个分支事务将自己的Branch ID分支事务ID与XID关联

4.第二阶段全局事务提交,TC会通知各个分支参与者提交分支事务,在第一阶段就已经提交了分支事务,这里各参与者只需要删除UNDO_LOG即可,并且可以异步执行,第二阶段很快可以完成。

5.第二阶段全局事务回滚,TC会通知各个分支参与者回滚分支事务,通过XID和Branch lD找到相应的回滚日志,通过回滚日志生成反向的SQL并执行,以完成分支事务回滚到之前的状态,如果回滚失败则会重试回滚操作。

Seata工程构建

1.根据前面的步骤将我们的Seata-Server项目启动起来,也就是启动TC

2.在需要使用到分布式事务的微服务中引入Seata依赖,也就是注册TM和RM

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <!-- 版本较低,排除-->
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 使用高版本 -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency>

3.配置微服务配置文件,让微服务能通过注册中心找到seata-server

seata:
  enabled: true
  application-id: ${spring.application.name}
  #事务群组(可以每个应用独立取名,也可以使用相同的名字),根据这个获取tc服务的cluster名称。事务组=全局事务
  #"default_tx_group"是默认值,如果改了这里,server配置下的 `server.vgroupMapping.xxxx`也要跟着一起改,否则会注册不到 “seata-server”导致报错。
  tx-service-group: default_tx_group
  config:
    type: nacos
    #需要和server在同一个注册中心下
    nacos:
      namespace: a971c0d9-771c-4c21-b4f3-3c04c499d831
      server-addr: 127.0.0.1:8848
      #需要和server端的registry、config一致
      group: SEATA-GROUP
      username: nacos
      password: nacos
  registry:
    type: nacos
    nacos:
      #需要和server端保持一致,即server在nacos中的名称,默认为seata-server
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: DEFAULT_GROUP
      namespace: public
      username: nacos
      password: nacos

4.TM方法上加上@GlobalTransactional注解。

测试

1.订单服务出错回滚全局事务

如果我们将订单服务代码改为:

 那么只会扣减库存,而不会生成订单

 这两个不同的服务都存在本地事务,在本地事务中如果出现异常,那么就会回滚本地事务,且controller层会捕捉异常,不让异常抛给用户,所以订单服务即使发生异常回滚了,扣减库存的服务是感知不到的。

现在我们将更改扣减库存的服务,使得订单创建失败,也不会扣减库存

 可以使用状态码来判断远程调用是否成功或失败,判断是否需要提交或回滚

测试后,库存和订单数据都一致。因为全局事务回滚了。

 这个例子不是很明显,因为即使是本地事务回滚,也会将扣减库存的服务回滚,发挥不了分布式事务的优势。请看下面的例子。

2.扣减库存服务出错回滚订单创建

将创建订单的服务变为正常的:

 扣减库存的服务代码最后会抛出一个异常:

 如果没有分布式事务,那么就会发生库存没扣,但是订单却创建了的情况。但我们现在有全局事务,如果发生错误,TC会将所有的RM回滚,包括远端的服务。

如下这是订单服务的结果:

 虽然它本来在本地已经创建了一个订单,且提交了,但是TC在检测到一个RM发生错误时就会通知它,让它根据UNDO_LOG进行回滚操作。

总结:

  • 在需要发起全局事务的service方法上添加注解 @GlobalTransactional
  • 使用Fiegn、restTemplate等方式发送请求,提供方只添加@Transactional保证本地事务

注意问题:

1.seata通过线程变量XID(RootContext.getXID() )判断TC与RM是否在同一事务下,现支持使用restTemplate与Feign方式发送请求自动携带xid到被调用方,使用其他方式可将xid放入请求头中,key为”TX_XID“,RM会在请求头中自动获取。若采用其他方式需自行保证xid的传递。

2.被调用方产生异常却没有回滚:当被调用方RM产生异常时,为了调用方TM可以正确接收到异常状态码,使Feign能抛出异常发起全局事务回滚,RM最好不要添加异常处理去拦截异常。

Seata的TCC模式

TCC模式主要是要注意幂等性、悬挂、空回滚这三个问题。使用TCC就需要改变我们的原先的代码逻辑,我们需要先构思出整体的流程:

扣减库存的服务:

try:
     悬挂校验
     校验库存
     减库存
confirm:
     无
cancel:
     幂等校验
     空回滚处理
     加上库存

创建订单服务:

try:无
confirm:
     幂等校验
     创建订单
cancel:无
@LocalTCC
public interface ProductService {
    @TwoPhaseBusinessAction(name = "buy", commitMethod = "confirm", rollbackMethod = "cancel")
    boolean buy(@BusinessActionContextParameter(paramName = "id") Integer id);

    boolean confirm(BusinessActionContext ctx);

    boolean cancel(BusinessActionContext ctx);
}

具体的流程比较复杂,可以新建几个表来解决幂等性、空回滚、悬挂问题


版权声明:本文为weixin_45902285原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。