Spring Cloud Alibaba Seata安装+微服务实战

news2025/6/9 23:09:33

目录

    • 介绍
    • 核心功能
    • 三层核心架构
    • 安装
    • 微服务实战
      • 创建三个业务数据库
      • 编写库存和账户两个Feign接口
      • 订单微服务 seata-order-service9701
      • 库存微服务 seata-store-service9702
      • 账户微服务 seata-account-service9703
      • 测试结果
    • 总结

在这里插入图片描述

介绍


Spring Cloud Alibaba Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 为用户提供了AT(自动事务)、TCC(补偿事务)、SAGA(长事务) 和 XA(强一致性) 四种事务模式,为用户打造一站式的分布式解决方案。

核心功能


1. 分布式事务支持

  • AT 模式: 基于 SQL 解析自动生成回滚日志,适合高频事务场景(如订单支付)。
  • TCC 模式: 通过 Try-Confirm-Cancel 三阶段操作实现灵活控制,适合金融等强一致性场景。
  • SAGA 模式: 通过编排长流程事务,支持跨系统长时间运行的任务(如物流跟踪)。
  • XA 模式: 依赖数据库的 XA 协议,提供强一致性保障。

2. 高可用与可扩展性

  • 支持多节点部署,通过注册中心 (如 Nacos) 动态管理服务实例。
  • 提供灵活的配置方式,支持文件、数据库、Nacos 等多种配置源。

三层核心架构


1. TC(Transaction Coordinator,事务协调器)

  • 独立部署的服务端,负责全局事务的协调、提交和回滚。
  • 通过状态机维护事务生命周期,确保跨服务一致性。

2. TM(Transaction Manager,事务管理器)

  • 嵌入在客户端应用中,负责开启、提交或回滚全局事务。
  • 通过注解 (如 @GlobalTransactional) 标记需要分布式事务的方法。

3. RM(Resource Manager,资源管理器)

  • 管理本地事务,与 TC 交互注册分支事务并上报状态。
  • 支持主流数据库 (MySQL、Oracle 等) 和缓存 (Redis)。

安装


1. 官网下载

下载地址:Seate 下载

2. mysql8数据库中建库建表

创建seata库

CREATE DATABASE seata;
USE seata;

在seata数据库中建表,当seata的存储模式为db时才需要以下的建表操作
建表脚本地址:建表脚本

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
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_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store BranchSession data
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 = utf8mb4;

-- the table to store lock data
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),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

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);

在这里插入图片描述

3. 修改seata配置
在 seata-server-2.0.0 的 conf 目录下,先备份 application.yml 文件,再参考 application.example.yml 模板修改原来的 application.yml 文件,修改后的内容如下:

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${log.home:${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:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace:
      group: SEATA_GROUP #后续自己在nacos里面新建,不想新建SEATA_GROUP,就写DEFAULT_GROUP
      username: nacos
      password: nacos
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP #后续自己在nacos里面新建,不想新建SEATA_GROUP,就写DEFAULT_GROUP
      namespace:
      cluster: default
      username: nacos
      password: nacos    
  store:
    mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/seata?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
      user: root
      password: root
      min-conn: 10
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      query-limit: 1000
      max-wait: 5000
  #  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,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**

4. 启动 nacos-server-2.5.1 在 bin 目录下执行 startup.cmd -m standalone

5. 启动 seata-server-2.0.0 在 bin 目录下执行 seata-server.bat

  • 访问地址: http://localhost:7091
  • 用户名 / 密码: seata / seata

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

微服务实战


创建三个业务数据库

1. 订单数据库 seata_order,创建 t_order 和 undo_log 表

CREATE DATABASE seata_order;

USE seata_order;

CREATE TABLE t_order(
		`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
		`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:已完结'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

SELECT * FROM t_order;

-- for AT mode you must to init this sql for you business database. the seata server not need it.

CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

2. 库存数据库 seata_store,创建 t_store 和 undo_log 表

CREATE DATABASE seata_store;

USE seata_store;

CREATE TABLE t_store(
		`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
		`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 '剩余库存'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO t_store(`id`,`product_id`,`total`,`used`,`residue`)VALUES('1','1','100','0','100');

SELECT * FROM t_store;

-- for AT mode you must to init this sql for you business database. the seata server not need it.

CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

3. 账户数据库 seata_account,创建 t_account 和 undo_log 表

CREATE DATABASE seata_account;

USE seata_account;

CREATE TABLE t_account(
		`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
		`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 '0' COMMENT '剩余可用额度'
)ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO t_account(`id`,`user_id`,`total`,`used`,`residue`)VALUES('1','1','1000','0','1000');

SELECT * FROM t_account;

-- for AT mode you must to init this sql for you business database. the seata server not need it.

CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

在这里插入图片描述

undo_log回滚日志表脚本地址:undo_log建表脚本

编写库存和账户两个Feign接口

1. 通用模块 cloud-common-api 新增 StoreFeignApi 接口

@FeignClient("seata-store-service")
public interface StoreFeignApi {

    // 扣减库存
    @PostMapping(value = "/store/decrease")
    Result decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}

2. 通用模块 cloud-common-api 新增 AccountFeignApi 接口

@FeignClient("seata-account-service")
public interface AccountFeignApi {

    // 扣减账户余额
    @PostMapping("/account/decrease")
    Result decrease(@RequestParam("userId") Long userId, @RequestParam("money") Long money);
}

订单微服务 seata-order-service9701

1. 引入依赖

<!-- nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- alibaba-seata -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!-- openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- loadbalancer -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- cloud-common-api -->
<dependency>
    <groupId>com.zzyy.cloud</groupId>
    <artifactId>cloud-common-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<!-- web + actuator -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- mybatis和springboot整合 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- Mysql8数据库驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!--test-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

2. yml配置

server:
  port: 9701

# =============== applicationName + mysql8 driver ===============
spring:
  application:
    name: seata-order-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
    username: root
    password: root

# =============== mybatis-plus ===============
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    # mybatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# =============== seata ===============
seata:
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: SEATA_GROUP
      application: seata-server
  tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称
  service:
    vgroup-mapping:
      default_tx_group: default # 事务组与TC服务集群的映射关系
  data-source-proxy-mode: AT
  
logging:
  level:
    io:
      seata: info

3. 主启动类

@SpringBootApplication
@EnableDiscoveryClient  //服务注册与发现
@EnableFeignClients
@MapperScan("com.zzyy.cloud.mapper")
public class SeataOrderMain9701 {

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

4. 控制层 OrderController

@RestController
public class OrderController {

    @Resource
    private OrderService orderService;

    //创建订单
    @GetMapping("/order/create")
    public Result create(Order order) {
        orderService.create(order);
        return Result.success(order);
    }
}

5. 服务层 OrderServiceImpl

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService{

    @Resource
    private OrderMapper orderMapper;
    @Resource  //库存微服务Feign接口
    private StoreFeignApi storeFeignApi;
    @Resource  //账户微服务Feign接口
    private AccountFeignApi accountFeignApi;

    @Override
    @GlobalTransactional(name = "zzyy-create-order", rollbackFor = Exception.class) //AT
    public void create(Order order) {
        // xid全局事务id的检查,重要
        String xid = RootContext.getXID();
        //1.创建订单
        log.info("------------创建订单-开始-xid: " + xid);
        Order orderFromDB = null;
        //初始创建订单时默认订单状态为0
        order.setStatus(0);
        int i = orderMapper.insert(order);
        if (i > 0) {
            //从mysql中查出刚创建的订单记录
            orderFromDB = orderMapper.selectById(order.getId());
            //2.扣减库存
            storeFeignApi.decrease(orderFromDB.getProductId(), orderFromDB.getCount());
            //3.扣减账户余额
            accountFeignApi.decrease(orderFromDB.getUserId(), orderFromDB.getMoney());
            //4.修改订单状态,将订单状态由0修改为1,表示已完成
            log.info("------------修改订单状态-开始");
            orderFromDB.setStatus(1);
            orderMapper.updateById(orderFromDB);
            log.info("------------修改订单状态-结束");
        }
        log.info("------------创建订单-结束-xid: " + xid);
    }
}

库存微服务 seata-store-service9702

1. 引入依赖

库存微服务所需依赖可直接复制上述订单微服务相关依赖

2. yml配置

server:
  port: 9702

# =============== applicationName + mysql8 driver ===============
spring:
  application:
    name: seata-store-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_store?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
    username: root
    password: root

# =============== mybatis-plus ===============
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    # mybatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# =============== seata ===============
seata:
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: SEATA_GROUP
      application: seata-server
  tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称
  service:
    vgroup-mapping:
      default_tx_group: default # 事务组与TC服务集群的映射关系
  data-source-proxy-mode: AT

logging:
  level:
    io:
      seata: info

3. 主启动类

@SpringBootApplication
@EnableDiscoveryClient  //服务注册与发现
@EnableFeignClients
@MapperScan("com.zzyy.cloud.mapper")
public class SeataStoreMain9702 {
    
    public static void main(String[] args) {
        SpringApplication.run(SeataStoreMain9702.class, args);
    }
}

4. 控制层 StoreController

@RestController
public class StoreController {

    @Resource
    private StoreService storeService;

    //扣减库存
    @PostMapping("/store/decrease")
    public Result decrease(@RequestParam("productId") Long productId,
                           @RequestParam("count") Integer count) {
        storeService.decrease(productId, count);
        return Result.success("扣减库存成功");
    }
}

5. 服务层 StoreServiceImpl

@Service
@Slf4j
public class StoreServiceImpl extends ServiceImpl<StoreMapper, Store> implements StoreService{

    @Resource
    private StoreMapper storeMapper;

    @Override
    public void decrease(Long productId, Integer count) {
        log.info("------------扣减库存-开始");
        QueryWrapper<Store> wrapper = new QueryWrapper<>();
        wrapper.eq("product_id", productId);
        Store store = storeMapper.selectOne(wrapper);
        Integer used = store.getUsed() + count;
        store.setUsed(used);
        store.setResidue(store.getTotal() - used);
        storeMapper.updateById(store);
        log.info("------------扣减库存-结束");
    }
}

账户微服务 seata-account-service9703

1. 引入依赖

库存微服务所需依赖可直接复制上述订单微服务相关依赖

2. yml配置

server:
  port: 9703

# =============== applicationName + mysql8 driver ===============
spring:
  application:
    name: seata-account-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_account?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
    username: root
    password: root

# =============== mybatis-plus ===============
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    # mybatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# =============== seata ===============
seata:
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: SEATA_GROUP
      application: seata-server
  tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称
  service:
    vgroup-mapping:
      default_tx_group: default # 事务组与TC服务集群的映射关系
  data-source-proxy-mode: AT

logging:
  level:
    io:
      seata: info

3. 主启动类

@SpringBootApplication
@EnableDiscoveryClient  //服务注册与发现
@EnableFeignClients
@MapperScan("com.zzyy.cloud.mapper")
public class SeataAccountMain9703 {
    
    public static void main(String[] args) {
        SpringApplication.run(SeataAccountMain9703.class, args);
    }
}

4. 控制类 AccountController

@RestController
public class AccountController {

    @Resource
    private AccountService accountService;

    //扣减账户余额
    @PostMapping("/account/decrease")
    public Result decrease(@RequestParam("userId") Long userId,
                           @RequestParam("money") Integer money){
        accountService.decrease(userId, money);
        return Result.success("扣减账户余额成功");
    }
}

5. 服务层 AccountServiceImpl

@Service
@Slf4j
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService{

    @Resource
    private AccountMapper accountMapper;

    @Override
    public void decrease(Long userId, Integer money) {
        log.info("------------扣减账户余额-开始");
        QueryWrapper<Account> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId);
        Account account = accountMapper.selectOne(wrapper);
        Integer used = account.getUsed() + money;
        account.setUsed(used);
        account.setResidue(account.getTotal() - used);
        accountMapper.updateById(account);
        //模拟超时
//        try {
//            // 暂停62秒
//            TimeUnit.SECONDS.sleep(62);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        log.info("------------扣减账户余额-结束");
    }
}

测试结果

启动 Nacos、Seata、以及三个业务微服务
在这里插入图片描述
测试一

访问: http://localhost:9701/order/creata?userId=1&productId=1&count=10&money=100
返回: {“code”:“200”, “msg”: “success”, “data”: {“id”: 6, “userId”: 1, “productId”: 1, “count”: 10, “money”: 100, “status”: 0}, “timestamp”: 1748786900259}
结果: 订单、库存、账户表都新增1条记录,库存和账户余额都已扣减,订单状态为1表示已完成。

测试二

未使用@GlobalTransactional注解,在账户余额扣减逻辑后添加模拟超时逻辑,等待62秒(Feign的超时时间默认是60秒)
访问: http://localhost:9701/order/creata?userId=1&productId=1&count=10&money=100
返回: {“code”: “500”, “msg”: “Read timed out executing POST http://seata-account-service/account/decrease?userId=1&money=100”, “data”: null, “timestamp”: 1748788205704}
结果: 订单、库存、账户表都新增1条记录,库存和账户余额都已扣减,但订单状态为0表示未完成。

测试三

使用@GlobalTransactional注解,在账户余额扣减逻辑后添加模拟超时逻辑,等待62秒(Feign的超时时间默认是60秒)
访问: http://localhost:9701/order/creata?userId=1&productId=1&count=10&money=100
返回: {“code”: “500”, “msg”: “Read timed out executing POST http://seata-account-service/account/decrease?userId=1&money=100”, “data”: null, “timestamp”: 1748789082880}
结果: 超时触发事务回滚,订单、库存、账户表数据最终都未发生变化。

总结


以上主要介绍了 Seata 安装、微服务实战的相关知识,想了解更多 Seata 知识的小伙伴请参考 Seata 官网 和 Spring Cloud Alibaba 官网 进行学习,学习更多 Spring Cloud 实战实用技巧的小伙伴,请关注后期发布的文章,认真看完一定能让你有所收获。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2405951.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FMC STM32H7 SDRAM

如何无痛使用片外SDRAM? stm32 已经成功初始化了 STM32H7 上的外部 SDRAM&#xff08;32MB&#xff09; 如何在开发中无痛使用SDRAM 使它像普通 RAM 一样“自然地”使用? [todo] 重要 MMT(Memory Management Tool) of STM32CubeMx The Memory Management Tool (MMT) disp…

部署DNS从服务器

部署DNS从服务器的目的 DNS域名解析服务中&#xff0c;从服务器可以从主服务器上获得指定的区域数据文件&#xff0c;从而起到备份解析记录与负载均衡的作用&#xff0c;因此通过部署从服务器可以减轻主服务器的负载压力&#xff0c;还可以提升用户的查询效率。 注意&#xf…

Android Camera Hal中通过Neon指令优化数据拷贝

背景描述&#xff1a; Camera apk普通相机模式录像操作时&#xff0c;一般是同时请求两个流&#xff0c;即预览流和录像流。对于两个流输出图像格式和分辨率相同的情况下&#xff0c;是不是可以通过一个流拷贝得到另一个流的数据&#xff0c;进而节省掉一个Sensor输出处理两次…

C# winform教程(二)----button

一、button的使用方法 主要使用方法几乎都在属性内&#xff0c;我们操作也在这个界面 二、作用 用户点击时触发事件&#xff0c;事件有很多种&#xff0c;可以根据需要选择。 三、常用属性 虽然属性很多&#xff0c;但是常用的并不多 3.常用属性 名称内容含义AutoSize自动调…

Python编码格式化之PEP8编码规范

文章目录 概要PEP8编码风格py文本组织规范命名规范编码风格 PEP8编码检查工具pylintflake8PyCharm中配置检查工具 PEP8编码格式化工具blackautopep8PyCharm配置格式化工具本地git配置hook 总结 概要 在Python项目开发过程中&#xff0c;代码的可读性和一致性对于项目的长期维护…

【Zephyr 系列 14】使用 MCUboot 实现 BLE OTA 升级机制:构建安全可靠的固件分发系统

🧠关键词:Zephyr、MCUboot、OTA 升级、BLE DFU、双分区、Bootloader、安全固件管理 📌面向读者:希望基于 Zephyr 为 BLE 设备加入安全 OTA 升级功能的开发者 📊预计字数:5200+ 字 🧭 前言:为什么你需要 OTA? 随着设备部署数量增多与产品生命周期延长,远程升级(…

K8S认证|CKS题库+答案| 8. 沙箱运行容器 gVisor

目录 8. 沙箱运行容器 gVisor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、官网找模板 3&#xff09;、创建 RuntimeClass 4&#xff09;、 将命名空间为 server 下的 Pod 引用 RuntimeClass 5&#xff09…

Selenium4+Python的web自动化测试框架

一、什么是Selenium&#xff1f; Selenium是一个基于浏览器的自动化测试工具&#xff0c;它提供了一种跨平台、跨浏览器的端到端的web自动化解决方案。Selenium主要包括三部分&#xff1a;Selenium IDE、Selenium WebDriver 和Selenium Grid。 Selenium IDE&#xff1a;Firefo…

【论文解读】MemGPT: 迈向为操作系统的LLM

1st author: Charles Packer paper MemGPT[2310.08560] MemGPT: Towards LLMs as Operating Systems code: letta-ai/letta: Letta (formerly MemGPT) is the stateful agents framework with memory, reasoning, and context management. 这个项目现在已经转化为 Letta &a…

vb监测Excel两个单元格变化,达到阈值响铃

需求 在Excel中实现监控两个单元格之间的变化范围&#xff0c;当达到某个设定的值的范围内时&#xff0c;实现自动响铃提示。 实现&#xff1a; 首先设置Excel&#xff0c;开启宏、打开开发者工具&#xff0c;点击visual Basic按钮&#xff0c;然后在左侧双击需要监测的shee…

node 进程管理工具 pm2 的详细说明 —— 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录 7

前言 我以 Ubuntu Server 打造的 NodeJS 服务器为主题的系列文章&#xff0c;经过五篇博客&#xff0c;我们顺利的 安装了 ubuntu server 服务器&#xff0c;并且配置好了 ssh 免密登录服务器&#xff0c;安装好了 服务器常用软件安装, 配置好了 zsh 和 vim 以及 通过 NVM 安装…

Flask与Celery 项目应用(shared_task使用)

目录 1. 项目概述主要功能技术栈 2. 项目结构3. 环境设置创建虚拟环境并安装依赖主要依赖 4. 应用配置Flask应用初始化 (__init__.py)Celery应用初始化 (make_celery.py) 5. 定义Celery任务 (tasks.py)任务说明 6. 创建API端点 (views.py)API端点说明 7. 前端界面 (index.html)…

二叉树-226.翻转链表-力扣(LeetCode)

一、题目解析 翻转可以理解为树的左右子树交换&#xff0c;从根到叶子节点&#xff0c;但是这里交换的是链接的指针&#xff0c;而不是单纯的交换值&#xff0c;当出现nullptr时&#xff0c;也是可以交换链接的&#xff0c;交换值的话就不行了。 二、算法原理 依旧的递归&…

HarmonyOS Next 弹窗系列教程(3)

HarmonyOS Next 弹窗系列教程&#xff08;3&#xff09; 选择器弹窗 (PickerDialog) 介绍 选择器弹窗通常用于在用户进行某些操作&#xff08;如点击按钮&#xff09;时显示特定的信息或选项。让用户可以进行选择提供的固定的内容。 以下内容都属于选择器弹窗&#xff1a; …

【docker】Windows安装docker

环境及工具&#xff08;点击下载&#xff09; Docker Desktop Installer.exe &#xff08;windows 环境下运行docker的一款产品&#xff09; wsl_update_x64 &#xff08;Linux 内核包&#xff09; 前期准备 系统要求2&#xff1a; Windows 11&#xff1a;64 位系统&am…

无人机避障——感知部分(Ubuntu 20.04 复现Vins Fusion跑数据集)胎教级教程

硬件环境&#xff1a;NVIDIA Jeston Orin nx 系统&#xff1a;Ubuntu 20.04 任务&#xff1a;跑通 EuRoC MAV Dataset 数据集 展示结果&#xff1a; 编译Vins Fusion 创建工作空间vins_ws # 创建目录结构 mkdir -p ~/vins_ws/srccd ~/vins_ws/src# 初始化工作空间&#xf…

如何安装并使用RustDesk

参考&#xff1a; 搭建 RustDesk Server&#xff1a;打造属于自己的远程控制系统&#xff0c;替代 TeamViewer 和 ToDesk&#xff01; 向日葵、ToDesk再见&#xff01;自己动手&#xff0c;自建RustDesk远程服务器真香&#xff01; 通俗易懂&#xff1a;RustDesk Server的搭…

机器学习——随机森林算法

随机森林算法是一种强大的树集成算法&#xff0c;比使用单个决策树效果要好得多。 以下是生成树集成的方法&#xff1a;假设有一个大小为m的训练集&#xff0c;然后对于b1到B&#xff0c;所以执行B次&#xff0c;可以使用有放回抽样来创建一个大小为m的训练集。所以如果有10个…

【从零学习JVM|第二篇】字节码文件

前言&#xff1a; 通过了解字节码文件可以帮助我们更容易的理解JVM的工作原理&#xff0c;所以接下来&#xff0c;我们来介绍一下字节码文件。 目录 前言&#xff1a; 正确的打开字节码文件 字节码文件组成 1. 魔数&#xff08;Magic Number&#xff09; 2. 版本号&…

Fractal Generative Models论文阅读笔记与代码分析

何恺明分型模型这篇文章在二月底上传到arXiv预出版网站到现在已经过了三个月&#xff0c;当时我也听说这篇文章时感觉是大有可为&#xff0c;但是几个月不知道忙啥了&#xff0c;可能错过很多机会&#xff0c;但是亡羊补牢嘛&#xff0c;而且截至目前&#xff0c;该文章应该也还…