Seata之AT模式Nacos版实战(二)

导读:本篇文章讲解 Seata之AT模式Nacos版实战(二),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

概述

AT模式运行机制

AT模式的特点就是业务无侵入式,整体机制分二阶段提交

  • 两阶段提交协议的演变:
    • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
    • 二阶段:
      1. 提交异步化,非常快速地完成。
      2. 回滚通过一阶段的回滚日志进行反向补偿。
        在AT模式下,用户只需关注自己的业务SQL,用户的业务SQL作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。
        在这里插入图片描述

Seata具体实现步骤

  1. TM端使用@GlobalTransaction进行全局事务开启、提交、回滚。
  2. TM开始RPC调用远程服务。
  3. RM端seata-client通过扩展DataSourceProxy,实现自动生成undo_log与TC上报。
  4. TM告知TC提交/回滚全局事务。
  5. TC通知RM各自执行commit/rollback操作,同时清楚undo_log。
    在这里插入图片描述

编写AT模式代码

demo示例地址

RM实现

创建订单和库存服务的DB、数据表

订单库:st_order
库存库:st_storage

-- 库存服务DB执行
CREATE TABLE `tab_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 '已用库存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `tab_storage` (`product_id`, `total`,`used`)VALUES ('1', '96', '4');
INSERT INTO `tab_storage` (`product_id`, `total`,`used`)VALUES ('2', '100','0');

-- 订单服务DB执行
CREATE TABLE `tab_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`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

各数据库加入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 pom.xml依赖

demo1模型的pom.xml文件

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath/>
    </parent>

	......
    
	<properties>
        <springboot.verison>2.4.2.RELEASE</springboot.verison>
        <java.version>1.8</java.version>
        <mybatis.version>2.1.5</mybatis.version>
        <tk-mapper.version>4.1.5</tk-mapper.version>
        <seata.version>1.3.0</seata.version>
    </properties>
	
	......

	<!--demo01父模块中添加依赖-->
    <dependencyManagement>
        <dependencies>
            <!--Mybatis通用Mapper-->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper</artifactId>
                <version>${tk-mapper.version}</version>
            </dependency>
            
            <!--SpringCloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--Spring Alibaba Cloud-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

子模块order-server和storage-service的pom文件。

	<!--子模块order-service和storage-service的pom中添加nacos和seata依赖-->
	<dependencies>
        <!--nacos注册中心和配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <!--移除掉该starter中自带的依赖,该依赖版本较低-->
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--单独添加seata 1.3.0的依赖-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>
        
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
            <version>10.2.3</version>
        </dependency>
        
        <!--Mybatis通用Mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
	</dependencies>

nacos中增加事务群组配置

增加配置项:service.vgroupMapping.default-fescar-service-group,值为default(应该代表微服务集群名称,默认集群名称default)。
这个配置项,用来不同的微服务发现 协调者服务(seata-server)使用。
default-fescar-service-group格式为:{xxx}-fescar-service-group,多个微服务可以使用一个,也可以每个服务独立使用一个,为了简单这里多个微服务使用一个,下面yml配置会使用这个配置项。
在这里插入图片描述

yml配置

order-server的yml配置如下:

server:
  port: 6770
spring:
  application:
    name: order-service
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://127.0.0.1:3306/st_order?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.230.128:8848
        register-enabled: true
        namespace: 62f0ae5b-38ea-4a2d-bb71-70ccbc0a8c10
      config:
        server-addr: 192.168.230.128:8848
        enabled: true
        file-extension: yaml
        namespace: 62f0ae5b-38ea-4a2d-bb71-70ccbc0a8c10

seata:
  enabled: true
  application-id: ${spring.application.name}
  # 事务群组(可以每个应用独立取名,也可以使用相同的名字),要与nacos中的service.vgroupMapping的后缀对应;
  # 命名规则是固定的:vgroupMapping.[springcloud服务名]-fescar-service-group;
  # 源码根据这个命名规则获取应用服务的信息;
  tx-service-group: default-fescar-service-group
  config:
    type: nacos
    # 需要和server在同一个注册中心下
    nacos:
      namespace: 62f0ae5b-38ea-4a2d-bb71-70ccbc0a8c10
      serverAddr: 192.168.230.128:8848
      # 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
      group: SEATA_GROUP
      username: "nacos"
      password: "nacos"
  registry:
    type: nacos
    nacos:
      # 需要和server端保持一致,即server在nacos中的名称,默认为seata-server
      application: seata-server
      server-addr: 192.168.230.128:8848
      # 与nacos服务列表中的分组名称一致
      group: DEFAULT_GROUP
      namespace: 62f0ae5b-38ea-4a2d-bb71-70ccbc0a8c10
      username: "nacos"
      password: "nacos"

mybatis:
  mapperLocations: classpath:mapper/*.xml

storage-server的yml如下:

server:
  port: 6780
spring:
  application:
    name: storage-service
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://127.0.0.1:3306/st_storage?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.230.128:8848
        register-enabled: true
        namespace: 62f0ae5b-38ea-4a2d-bb71-70ccbc0a8c10
      config:
        server-addr: 192.168.230.128:8848
        enabled: true
        file-extension: yaml
        namespace: 62f0ae5b-38ea-4a2d-bb71-70ccbc0a8c10

seata:
  enabled: true
  application-id: ${spring.application.name}
  # 事务群组(可以每个应用独立取名,也可以使用相同的名字),要与服务端nacos-config.txt中service.vgroup_mapping的后缀对应
  tx-service-group: default-fescar-service-group
  config:
    type: nacos
    # 需要和server在同一个注册中心下
    nacos:
      namespace: 62f0ae5b-38ea-4a2d-bb71-70ccbc0a8c10
      serverAddr: 192.168.230.128:8848
      # 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
      group: SEATA_GROUP
      username: "nacos"
      password: "nacos"
  registry:
    type: nacos
    nacos:
      # 需要和server端保持一致,即server在nacos中的名称,默认为seata-server
      application: seata-server
      server-addr: 192.168.230.128:8848
      # 与nacos服务列表中的分组名称一致
      group: DEFAULT_GROUP
      namespace: 62f0ae5b-38ea-4a2d-bb71-70ccbc0a8c10
      username: "nacos"
      password: "nacos"

mybatis:
  mapperLocations: classpath:mapper/*.xml

若出现bootstarp.yml中文注释报以下错误的情况,则需要调整编码,如下:

Caused by: org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 1
Caused by: java.nio.charset.MalformedInputException: Input length = 1

在这里插入图片描述

编写业务代码

package com.it235.seata.order;

import tk.mybatis.spring.annotation.MapperScan;

@RestController
@SpringBootApplication
@MapperScan("com.it235.seata.order.mapper")
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServiceApplication {

    @Autowired
    private OrderService orderService;

    @GetMapping("order/create")
    public Boolean create(long userId , long productId){
        Order order = new Order();
        order.setCount(1)
            .setMoney(BigDecimal.valueOf(88))
            .setProductId(productId)
            .setUserId(userId)
            .setStatus(0);
        return orderService.create(order);
    }

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Override
    public boolean create(Order order) {
        log.info("创建订单开始");
        int index = orderMapper.insert(order);
        log.info("创建订单结束");
        return index > 0;
    }
}
@Table(name = "tab_order")
@Data
@Accessors(chain = true)
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long userId;

    private Long productId;

    private int count;

    private BigDecimal money;

    private int status;
}
@Repository
public interface OrderMapper extends Mapper<Order> {

}

浏览器访问模拟添加订单请求,http://localhost:6770/order/create,查看数据库,此时单个服务搭建完成

依葫芦画瓢,搭建库存服务,同时保证库存服务正常启动注册

TM实现

搭建business服务,pom、bootstrap.yml与RM基本一致,并提供FeignClient调用组件

# feign组件超时设置,用于查看seata数据库中的临时数据内容
feign:
  client:
    config:
      default:
        connect-timeout: 30000
        read-timeout: 30000

FeignClient组件代码编写

@FeignClient(name = "storage-service")
@Component
public interface StorageClient {
    
    @GetMapping("storage/change")
    Boolean changeStorage(@RequestParam("productId") long productId ,@RequestParam("used")  int used);
}

@FeignClient(name = "order-service")
@Component
public interface OrderClient {
    
    @GetMapping("order/create")
    Boolean create(@RequestParam("userId") long userId ,@RequestParam("productId") long productId);
}

调用层代码

@SpringBootApplication
@RestController
@EnableFeignClients
@EnableDiscoveryClient
public class BusinessServiceApplication {

    @Autowired
    private OrderClient orderClient;
    @Autowired
    private StorageClient storageClient;

    @GetMapping("buy")
    @GlobalTransactional
    public String buy(long userId , long productId){
        orderClient.create(userId , productId);
        storageClient.changeStorage(userId , 1);
        return "ok";
    }

    public static void main(String[] args) {
        SpringApplication.run(BusinessServiceApplication.class, args);
    }
}

参考

AT模式详解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/100052.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!