springcloud+seata1.4.0+nacos整合详细教程


本文档默认您已经具备如下环境:

一、环境准备

1.1 资源
名称地址
Nacoshttps://github.com/alibaba/nacos/tags注册中心、配置中心
seata1.4.0http://seata.io/zh-cn/blog/download.html分布式事务
项目示例https://github.com/HLDBanana/seata_demoseata示例项目
1.2 seata介绍

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
AT可以看作时由Seata社区进行全方面优化,自研的XA模式。最大特点就是解决了XA模式的性能差的问题。

1.2.1 AT模式

前提

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

整体机制

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

写隔离

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

以一个示例来说明:

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
在这里插入图片描述
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
在这里插入图片描述
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

工作机制

一阶段

  1. 解析 SQL得到前镜像(原始数据):
  2. 执行业务 SQL:更新数据
  3. 查询后镜像(更新后的数据)
  4. 把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中。
  5. 提交前,向 TC 注册分支:该条数据的 全局锁 。
  6. 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
  7. 将本地事务提交的结果上报给 TC。

二阶段-回滚
收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。

  1. 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
  2. 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
    update product set name = ‘TXC’ where id = 1;
  3. 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

二阶段-提交

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

TCC 模式,不依赖于底层数据资源的事务支持:

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

在这里插入图片描述

1.2.3 SAGA模式

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

  • 适用场景:
    业务流程长、业务流程多

在这里插入图片描述

二、seata AT模式整合

2.1 创建业务表、undo_log回滚日志表、事务锁信息库表

新版本seata-server包内不含 undo_log、事务表建表sql,需要下载seata-server-0.9.0 版本获取响应sql文件:
seata-server-0.9.0.zip

  • 创建file.conf对应的事务锁信息表
#全局事务表
CREATE TABLE `branch_table` (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `lock_key` varchar(128) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='全局事务表';

#分支事务表
CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分支事务表';

#表数据锁信息(具体加锁的表数据id)
CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(96) DEFAULT NULL,
  `transaction_id` mediumtext,
  `branch_id` mediumtext,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='存储锁表';

如下:
在这里插入图片描述

  • 创建业务表单
创建seata-account、seata-order、seata-storage业务库,分别创建t_account、t_ordre、t_storage业务表单
# 账户表
CREATE TABLE seata-account.t_account (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
  `total` decimal(10,0) DEFAULT NULL COMMENT '总额度',
  `used` decimal(10,0) DEFAULT NULL COMMENT '已用额度',
  `residue` decimal(10,0) DEFAULT NULL COMMENT '剩余可用额度',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

#订单表
CREATE TABLE seata-order.t_order (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
  `product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
  `count` int(11) DEFAULT NULL COMMENT '数量',
  `money` decimal(11,0) DEFAULT NULL COMMENT '金额',
  `status` int(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

#库存表
CREATE TABLE seata-storage.t_storage (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
  `total` int(11) DEFAULT NULL COMMENT '库存',
  `used` int(11) DEFAULT NULL COMMENT '已用库存',
  `residue` int(11) DEFAULT NULL COMMENT '剩余库存',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

  • 业务库创建undo_log 回滚日志表
-- 注意此处0.3.0+ 增加唯一索引 ux_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  COMMENT='AT transaction mode undo table';;

创建完成如下:
在这里插入图片描述

2.2 seata-server配置

下载seata-server: http://seata.io/zh-cn/blog/download.html

  • 配置seata-seaver 注册中心
    修改seata/conf/registry.conf 配置文件
    只需修改选中配置中心类型对应配置即可,如下:
    在这里插入图片描述
  • seata-server 事务日志信息
    修改seata/conf/file.conf 配置文件
    只需修改选中配置中心类型对应配置即可,如下:
    在这里插入图片描述
2.3 将配置导入到nacos

seata-server-0.9.0版本包中有上传nacos配置中心的脚本以及配置模板:seata-server-0.9.0.zip
如图:
在这里插入图片描述

  • 修改nacos-config.txt配置
    在这里插入图片描述
  • 上传配置到nacos
    上传配置到nacos seata命名空间,cmd进入nacos-config.sh所在路径执行:
#-t  命名空间id,如果需要的话请在nacos上自行创建命名空间并替换命名空间ID
#-h  nacos地址
#-g  nacos分组
sh nacos-config.sh -h 182.92.219.202 -p 8848 -g SEATA_GROUP -u nacos -w nacos -t 51915a62-d2d6-43d4-8f45-86b159eb90f5

如图:
在这里插入图片描述
将配置上传到了seata分支
在这里插入图片描述

三、项目配置

3.1 pom依赖
  • 父工程依赖
			<dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
  • 子工程依赖
		<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
3.2 子项目集成seata配置
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 182.92.219.202:8848		#nacos地址
        username: nacos							#nacos账号密码
        password: nacos
        group: SEATA_GROUP						#分组
#        namespace: 6c990727-93b2-4081-a8c6-6b015c56eda2	#不指定命名空间。默认public
seata:
  tx-service-group: my_test_tx_group		#这里要特别注意和nacos中配置的要保持一直
  registry:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
#      namespace: 6c990727-93b2-4081-a8c6-6b015c56eda2
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      username: ${spring.cloud.nacos.discovery.username}
      password: ${spring.cloud.nacos.discovery.password}
      group: ${spring.cloud.nacos.discovery.group}
      namespace: 51915a62-d2d6-43d4-8f45-86b159eb90f5   #2.2中配置所在命名空间ID,入未配置 默认public空间
  service:
    vgroup-mapping:
      my_test_tx_group: default		# 这里要特别注意和nacos中配置的要保持一直

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

四、编写业务代码

示例项目https://github.com/HLDBanana/seata_demo

业务逻辑图如下:
在这里插入图片描述
最终项目结构如下:
在这里插入图片描述

  • 仓储服务
    @PostMapping(value = "/decrease")
    public CommonResult decrease(@RequestParam("productId") @Valid @NotNull Long productId,
                                 @RequestParam("count") Integer count) {
        log.info("开始调用扣减库存");
        int suc = storageService.decrease(productId,count);
        log.info("扣减成功:" + suc);
        return new CommonResult(200, "扣减库存成功");
    }
  • 账户服务
    @PostMapping("/decrease")
    public CommonResult decrease(@RequestParam("userId") Long userId,
                                 @RequestParam("money") BigDecimal money) {
        log.info("开始调用扣减账户余额");
        int res =  accountService.decrease(userId, money);
        log.info("扣减金额成功:" + res);
        return new CommonResult(res, "扣减账户余额成功");
    }

  • 订单服务
订单服务
public interface OrderService {

    /**
     * 创建订单
     */
    void create(Order order);
}
  • 主要调用逻辑
    在需要开启分布式事务的方法上添加@GlobalTransactional注解,开启分布式事务。
    注意:被调用服务不需要做任何调整,只需要在调用方开启分布式事务就可以了
 @Override
    @GlobalTransactional(name = "seata-create-order", rollbackFor = Exception.class)
    public void create(Order order) {
        log.info("--------->开始新建订单");
        //1 新建订单
        orderDao.create(order);
        //2 扣减库存
        log.info("------------->订单微服务开始调用库存,做扣减Count");
        CommonResult st = storageService.decrease(order.getProductId(), order.getCount());
        if (st.getCode() != 200){
            throw new  RuntimeException("扣减库存失败");
        }
        log.info("------------->订单微服务开始调用库存,做扣减end");
        //3 扣减账户
        log.info("------------->订单微服务开始调用账户,做扣减Money");
        CommonResult ac = accountService.decrease(order.getUserId(), order.getMoney());
        if (ac.getCode() != 200){
            throw new  RuntimeException("扣减账户失败");
        }
        log.info("------------->订单微服务开始调用账户,做扣减end");

        //4 修改订单状态
        log.info("------------->修改订单状态开始");
        orderDao.update(order.getUserId(),0);
        log.info("------------->修改订单状态结束");

        log.info("------------->下订单结束了");
    }

五、验证

5.1 二阶段-提交验证

调用接口:http://localhost:2001/order/create 创建订单扣减库存和账户金额
结果如下:
在这里插入图片描述

5.2 二阶段-回滚
  • 修改扣减账户余额实现方法,模拟超时
    @Override
    public int decrease(Long userId, BigDecimal money) {
        try {
            // 模拟超时异常
            TimeUnit.SECONDS.sleep(30000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //int a = 1/0;
        return accountDao.decrease(userId, money);
    }
  • 调用 http://localhost:2001/order/create 接口创建订单扣减库存和账户金额
    在这里插入图片描述
  • 查看数据库订单、库存本地事务是否已提交
    订单本地事务已提交
    在这里插入图片描述
    库存扣减成功
    在这里插入图片描述
  • 查看全局锁、分支锁、表数据锁
    查看seata库表锁状态信息
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 查看order库、storage库 undo_log回滚日志
    在这里插入图片描述

  • 放开断点,让代码逻辑执行完成,查看回滚情况。

代码执行结果:账户余额扣减超时,事务失败。
在这里插入图片描述
在这里插入图片描述
回滚完成之后 undo_log,锁信息全部清除
在这里插入图片描述

至此,验证完成。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值