seata分布式事务之AT模式实践代码

seata框架

AT模式:

先添加seata需要的数据库相关表,AT模式需要在每个业务所属库下建undo_log表,用来回滚的,出错seata就会从这个表生成反向sql回退数据

在这里插入图片描述

建表语句:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime(0) NOT NULL,
  `log_modified` datetime(0) NOT NULL,
  `ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

在每个业务库下建完undo_log后,需要建一个seata服务所用的库,我这里就叫seata库

在这里插入图片描述

建表语句:(注意我这个是1.6.1版本的表,可能和旧版本的字段长度什么的有差异,因为我之前随便找了sql跑seata就是报错,具体没去研究)

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for branch_table
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table`  (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `branch_type` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `gmt_create` datetime(6) DEFAULT NULL,
  `gmt_modified` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`branch_id`) USING BTREE,
  INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for distributed_lock
-- ----------------------------
DROP TABLE IF EXISTS `distributed_lock`;
CREATE TABLE `distributed_lock`  (
  `lock_key` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `lock_value` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `expire` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`lock_key`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table`  (
  `xid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `transaction_service_group` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `transaction_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `gmt_create` datetime(0) DEFAULT NULL,
  `gmt_modified` datetime(0) DEFAULT NULL,
  PRIMARY KEY (`xid`) USING BTREE,
  INDEX `idx_status_gmt_modified`(`status`, `gmt_modified`) USING BTREE,
  INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table`  (
  `row_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `xid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `branch_id` bigint(20) NOT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `table_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `pk` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0:locked ,1:rollbacking',
  `gmt_create` datetime(0) DEFAULT NULL,
  `gmt_modified` datetime(0) DEFAULT NULL,
  PRIMARY KEY (`row_key`) USING BTREE,
  INDEX `idx_status`(`status`) USING BTREE,
  INDEX `idx_branch_id`(`branch_id`) USING BTREE,
  INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

接下来采用nacos作为注册中心和配置中心,seata解压后修改conf下的application.yml

样例:

#  Copyright 1999-2019 Seata.io Group.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  extend:
    logstash-appender:
      destination: 127.0.0.1:4560
    kafka-appender:
      bootstrap-servers: 127.0.0.1:9092
      topic: logback_to_logstash

console:
  user:
    username: seata
    password: seata

#主要注意这一段:
seata:
  config: #配置中心,这里指定nacos
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
          serverAddr: 127.0.0.1:8848
          group:  SEATA_GROUP #nacos上的配置所属分组
          username: ""#nacos这里未开启验证无所谓配不配
          password: ""
          # data-id: seataServer.properties #我测的这个版本1.6.1将官方的config.txt写在这个里seata读不到,不知道原因,后采用旧版本seata的配置方式
          namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7 #配置的命名空间,需要现在nacos中新建命名空间
  registry:#nacos上的服务注册中心
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos #类别选择nacos
    nacos: 
          application: seata-server #注册到nacos上的服务名
          serverAddr: 127.0.0.1:8848
          group: SEATA_GROUP #服务所属组
          namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7 #所属命名空间,这里和配置所属的命名空间要一样
          username: ""#nacos这里未开启验证无所谓配不配
          password: ""



########这里的store我配了但是seata好像读不到,不知道为什么,后面还是在nacos上配的
  store:
    # support: file 、 db 、 redis
    mode: db
    db:
      db-type: mysql
      datasourcec: druid
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/seata?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
      user: root
      password: root
##############################################      
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

修改完application.yml后windows直接启动bin下的bat文件,然后可以看到nacos上由seata-server注册上来

在这里插入图片描述

然后到配置中心取做配置
在这里插入图片描述

注意,service.vgroupMapping.xxx,这个xxx很重要,对应意思是事务分组,

我以service.vgroupMapping.default_tx_group为例,这个default_tx_group也是1.6.1版本默认的配置

在这里插入图片描述

这里的default其实在seata启动的application.yml中可以配其他的,对应的是这个值

在这里插入图片描述

不配的话默认也是default,像上文我就没额外自己配

到这里seata-server端就启动好了,这里nacos更新配置文件seata是会动态更新的不用重启

接下来是客户端集成,就是微服务中集成seata模块

这边我以商品订单余额为例:

在这里插入图片描述

三个微服务,都注册在nacos上,分别为三个独立数据库

在这里插入图片描述

我的调用顺序为:减库存->减余额->创建订单

减库存接口:

   @Autowired
    ReduceGoodsService reduceGoodsService;

    @RequestMapping("/reduceGoods/{goodsId}")
    public String reduceGoods(@PathVariable("goodsId")String goodsId){
        System.out.println("调用扣减库存");
        int i = reduceGoodsService.reduceGoodsCount(goodsId);
        if (i==1){
            return "扣减库存成功";
        }
        else {
            return "扣减库存失败";
        }
    }

减余额接口:

  @Autowired
    ReduceMoneyService reduceMoneyService;

    @RequestMapping("/reduceMoney/{userId}")
    public String reduceMoney(@PathVariable("userId")String userId){
        System.out.println("调用扣减余额");
        int i = reduceMoneyService.reduceRestMoney(userId);

        if (i==1){
            return "扣减库存成功";
        }
        else {
            return "扣减库存失败";
        }
    }

创建订单接口:

  	@GlobalTransactional(rollbackFor = Exception.class)//这是全局事务注解***
    @RequestMapping("/createOrder/{userId}/{goodsId}/{goodsCount}")
    public void createOrder(@PathVariable("userId")String userId,@PathVariable("goodsId")String goodsId,@PathVariable("goodsCount")String goodsCount){


        //减库存
        String reduceGoodsUrl="http://"+"reduceGoods"+"/reduceGoods/"+goodsId;
        restTemplate.getForObject(reduceGoodsUrl, String.class);

        //减余额
        String reduceMoneyUrl="http://"+"reduceMoney"+"/reduceMoney/"+userId;
        restTemplate.getForObject(reduceMoneyUrl, String.class);

        createOrderService.createOrder(UUID.randomUUID().toString(),userId, goodsId, goodsCount);



    }

接下是重要的配置文件,以扣余额为例

server:
  port: 2222
spring:
  application:
    name: reduceMoney
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        ip: 127.0.0.1
        register-enabled: true
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/users?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource

mybatis:
  mapper-locations: classpath:mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true
##########################################以上是注册服务到nacos等的相关配置,主要关注下面的seata
seata:
  enable-auto-data-source-proxy: true   #默认开启,如果要手动配configuration中的datasource的话要设为false
  enabled: true
  tx-service-group: default_tx_group  #这个要和上文在nacos上注册的service.vgroupMapping.xxx中的xxx一致
  ##############################下面这部分和seata自己的那部分配置一样
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
  registry:
    type: nacos
    nacos:
        application: seata-server
        server-addr: 127.0.0.1:8848
        group: SEATA_GROUP
        namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
        username: ""
        password: ""
  ###################################
  service:
    # 事务组对应的集群名称
    vgroupMapping:
      default_tx_group: default  #这个default_tx_group和上面一样配你配在nacos中的service.vgroupMapping.xxx中的xxx一致


其他模块的seata配置和这个一样

接下来启动三个模块,然后调用,如果发现有异常,那seata会回退全局事务

在日志中体现为
在这里插入图片描述

如果全局事务提交成功,日志体现为
在这里插入图片描述

另外,在三个模块启动时,会分别注册到seata中去,日志体现为:

附录:一些坑:

如果模块启动报找不到service.vgroupMapping.xxxx大概率是配置中心配的不对,就是那个service.vgroupMapping.xxxx属性,然后服务模块中的yml一定要配这个

 ###################################
  service:
    # 事务组对应的集群名称
    vgroupMapping:
      default_tx_group: default 

业务表如果没主键,或者主键是自增的好像seata是不能用的,如果你的整理业务流程中,涉及到error你去捕获了但是不抛出来,那seata是不会认为报错的,也就不会回滚,用try catch的话一定要throw出来

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值