seata1.4.1-nacos部署使用及总结

官网

官网

下载地址

在这里下载
我使用的是1.4.1版本
懒得开虚拟机了,所以我下载的是.zip文件,其实使用起来没啥区别,在Windows上启动使用seata-server的批处理文件,在Linux上是使用.sh文件,就这点区别

配置

下载好后,我们看一下seata/conf目录下的文件
在这里插入图片描述
这里有两个README,其中这个-zh的是中文的,我们打开看看里边写的内容。

# 脚本说明

## [client](https://github.com/seata/seata/tree/develop/script/client) 

> 存放用于客户端的配置和SQL

- at: AT模式下的 `undo_log` 建表语句
- conf: 客户端的配置文件
- saga: SAGA 模式下所需表的建表语句
- spring: SpringBoot 应用支持的配置文件

## [server](https://github.com/seata/seata/tree/develop/script/server)

> 存放server侧所需SQL和部署脚本

- db: server 侧的保存模式为 `db` 时所需表的建表语句
- docker-compose: server 侧通过 docker-compose 部署的脚本
- helm: server 侧通过 Helm 部署的脚本
- kubernetes: server 侧通过 Kubernetes 部署的脚本

## [config-center](https://github.com/seata/seata/tree/develop/script/config-center)

> 用于存放各种配置中心的初始化脚本,执行时都会读取 `config.txt`配置文件,并写入配置中心

- nacos: 用于向 Nacos 中添加配置
- zk: 用于向 Zookeeper 中添加配置,脚本依赖 Zookeeper 的相关脚本,需要手动下载;ZooKeeper相关的配置可以写在 `zk-params.txt` 中,也可以在执行的时候输入
- apollo: 向 Apollo 中添加配置,Apollo 的地址端口等可以写在 `apollo-params.txt`,也可以在执行的时候输入
- etcd3: 用于向 Etcd3 中添加配置
- consul: 用于向 consul 中添加配置

我这里使用的是客户端AT模式,服务端db模式,MySQL。
所以我们得到服务端的sql脚本如下:

-- -------------------------------- 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_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- 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 = utf8;

-- 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),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

然后创建一个服务端的数据库seata,执行上述脚本,得到数据库
在这里插入图片描述
得到的客户端sql脚本:

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 = utf8 COMMENT ='AT transaction mode undo table';

我们这里创建三个客户端的数据库seata_account、seata_order和seata_storage,然后分别在这三个数据库中执行上述脚本
以上就是使用seata时必要的数据库配置
接下来我们看readme文档中的第三项,config.txt
这一项是用于向配置中心添加配置时使用的,我这里使用的nacos,并且以python的方式执行脚本,所以我们在得到config.txt之后选择的是nacos/nacos-config.py
下载后,修改config.txt中的store.db.url项,使之指向刚才创建的seata数据库
然后修改nacos-config.py,将画框位置的值指向自己config.txt所在位置
在这里插入图片描述
然后在命令窗口中使用python命令执行nacos-config.py脚本:

python nacos-config.py

执行结束后,我们打开nacos的控制台,会生成一大堆这样的配置
在这里插入图片描述
我们再回到seata/conf目录中,修改file.conf和registry.conf配置文件(是的,配置非常繁琐)
先看file.conf
在这里插入图片描述

我们这里使用的是db模式,所以mode的值改为db
然后在db的配置中,将url、user和password的值,改为我们刚才创建的seata数据库的

接着我们看registry.conf文件
我们这里是使用nacos,所以将registry项中的mode值设置为nacos,而对应的nacos项中的地址,改为自己nacos服务的地址
在这里插入图片描述
到这里还没完,往下还有一个config项
这一项也是,mode改为nacos,然后对应的nacos中的地址改为自己nacos服务的地址

到这里,服务端的配置算完事了,接着启动seata服务,启动之后没有报错就OK了

创建客户端

这里我们创建三个服务,分别是order,account和storage
我们要做的测试流程是,order服务接受一个订单,在order数据库中插入一条订单信息,然后order服务调用account服务,在账户中修改已使用的金额和剩余金额,返回后再调用storage服务,修改库存使用量和剩余量
在这里插入图片描述

创建order服务

先在我们之前创建的seata_order数据库中创建订单表

CREATE TABLE `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`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

然后创建订单的微服务,结构如下:
在这里插入图片描述
依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.czz</groupId>
    <artifactId>seata-order</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>seata-order</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>3.0.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-openfeign-core</artifactId>
            <version>2.2.1.RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <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>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

bootstrap.yml:nacos的配置中心必须放在bootstrap.yml中配置,索性我就把nacos的配置都放进来了

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yaml
  profiles:
    active: dev

application.yml:这其中的数据库链接配置和mybatis配置不是咱们这篇文章的重点,主要是cloud下边的这些,tx-service-group: my_test_tx_group这一项的值之所以是my_test_tx_group,这个不是我们随便写的,还记得上边我们讲过的config.txt么,这其中有一项service.vgroupMapping.my_test_tx_group=default,看到这其中的关系了把,也就是说vgroupMapping这后边是什么值,我们在yml里就得配置什么值;

server:
  port: 8041

spring:
  application:
    name: seata-order
  cloud:
    alibaba:
      seata:
        tx-service-group: my_test_tx_group
#        service:
#          vgroup-mapping:
#            my_test_tx_group: default
  datasource:
    url: jdbc:mysql://192.168.0.100:3306/seata_order?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver


mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.czz.seataorder.entity

启动类:

@MapperScan("**.dao.**")
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class SeataOrderApplication {

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

dao层就不说了,反正是往t_order表里插入一条数据
AccountFeignClient和StorageFeignClient

@FeignClient(name = "seata-account")
public interface AccountFeignClient {
    @GetMapping("/reduce")
    Boolean reduce(@RequestParam("userId") Long userId, @RequestParam("money")BigDecimal money);
}
@FeignClient(name = "seata-storage")
public interface StorageFeignClient {
    @GetMapping("/storage/deduct")
    Boolean deduct(@RequestParam("productId")Long productId, @RequestParam("count")int count);
}

要注意feign的接口要和对应服务接口的请求地址,参数列表一致,这个不是本文的重点,但还是需要提一句。
service层:@GlobalTransactional意味着全局事务的开始,在一次事务的调用链中只能有一个这个注解出现,并且在事务链中的所有子事务都必须带有 @Transactional 注解

@Service
public class OrderService {
    @Autowired
    private IOrderDao iOrderDao;

    @Autowired
    private AccountFeignClient accountFeignClient;

    @Autowired
    private StorageFeignClient storageFeignClient;

    @GlobalTransactional
    @Transactional
    public void insertOrder(Long userId, Long productId, int count){
        double singlePrice = 0.5;
        BigDecimal bigDecimal = new BigDecimal(singlePrice * count);
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        order.setCount(count);
        order.setPrice(bigDecimal);
        order.setStatus(0);
        accountFeignClient.reduce(userId,bigDecimal);
        storageFeignClient.deduct(productId, count);
        iOrderDao.insert(order);
        //int i = 1/0;
    }
}

controller层:

@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;

    @GetMapping("/orderSmt")
    public String orderSmt(){

        orderService.insertOrder(1L,1L,1);
        return "done";
    }
}

创建account服务

account服务的依赖和yml配置基本和order服务的一直,不同的只有服务名称和mybatis的配置,在这里我就不赘述了。
在seata-account库中创建t_account表:

CREATE TABLE `t_account` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(11) DEFAULT NULL,
  `total` decimal(10,0) DEFAULT NULL,
  `used` decimal(10,0) DEFAULT NULL,
  `residue` decimal(10,0) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

插入一条数据:

INSERT INTO `t_account` VALUES (1, 1, 1000, 0, 1000);

启动类:

@MapperScan("**.dao.**")
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class SeataAccountApplication {

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

}

service类:这里我们之前说过了,需要在每个子事务中使用 @Transactional注解

@Service
public class AccountService {
    @Autowired
    private IAccountDao iAccountDao;
    @Transactional
    public void reduce(Long userId){
        iAccountDao.update(userId);
    }
}

controller类:

@RestController
public class AccountController {
    @Autowired
    private AccountService accountService;
    @GetMapping("/reduce")
    public Boolean reduce(@RequestParam("userId")Long userId, @RequestParam("money")BigDecimal money){
        accountService.reduce(userId);
        return true;
    }
}

创建storage服务

配置不多说了,和上边一样
在seata_storage库中创建t_storage表

CREATE TABLE `t_storage` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(11) DEFAULT NULL,
  `total` int(11) DEFAULT NULL,
  `used` int(11) DEFAULT NULL,
  `residue` int(11) DEFAULT NULL COMMENT '剩余库存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

插入一条数据:

INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100);

启动类:

@MapperScan("**.dao.**")
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class SeataStorageApplication {

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

}

service类:

@Service
public class StorageService {
    @Autowired
    private IStorageDao iStorageDao;

    @Transactional
    public void deduct(Long productId, int count){
        iStorageDao.update(productId);
    }
}

controller类:

@RestController
public class StorageController {
    @Autowired
    private StorageService storageService;

    @GetMapping("/storage/deduct")
    public Boolean deduct(@RequestParam Long productId, @RequestParam int count){
        storageService.deduct(productId, count);
        return true;
    }
}

测试

先测试正常情况,我们在浏览器中输入订单请求地址:http://localhost:8041/orderSmt
在这里插入图片描述
没有报错,然后我们看下数据库保存的数据情况(因为我点了好几次,所以数据库里产生了好几条记录):
t_order表
在这里插入图片描述
t_account表:
在这里插入图片描述
t_storage表:
在这里插入图片描述
以上可以看到在正常情况下数据落地没有问题,接下来我们测试一下,在全局事务结束前程序报错了,看看事务是否生效
我们修改下order服务的service类,在执行完所有子事务之后,我们抛出个异常,再看看数据是否有变化

@GlobalTransactional
@Transactional
public void insertOrder(Long userId, Long productId, int count){
    double singlePrice = 0.5;
    BigDecimal bigDecimal = new BigDecimal(singlePrice * count);
    Order order = new Order();
    order.setUserId(userId);
    order.setProductId(productId);
    order.setCount(count);
    order.setPrice(bigDecimal);
    order.setStatus(0);
    accountFeignClient.reduce(userId,bigDecimal);
    storageFeignClient.deduct(productId, count);
    iOrderDao.insert(order);
    int i = 1/0; //我们在这里让程序出个异常
}

重启order服务,然后再调用订单接口
在这里插入图片描述
如我们所愿,报错了,接下来看看数据的情况
t_order表:
在这里插入图片描述
还是6条

t_account表:
在这里插入图片描述
没有变化

t_storage表:
在这里插入图片描述
还是没有变化,也就是说,我们的事务时生效了的
以上就是seata的部署及使用的方法

总结

先贴一个官网的架构图
在这里插入图片描述
解释一下其中的术语:
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

seata的工作过程大概是这样的,首先是TM开始全局事务,并向TC申请一个事务ID,在向下调用的时候会传播这个事务ID,RM拿到这个事务ID后与TC建立沟通,当所有分支事务全部安全完成时,由TC通知个分支事务的RM统一提交事务;当任一分支事务产生异常时,会向TC报告,然后TC通知所有RM回滚事务

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值