Spring Cloud Alibaba Seata 实现分布式事物

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案

Seata 官网:https://seata.io/zh-cn/

Spring Cloud Alibaba 官网:https://sca.aliyun.com/zh-cn/

版本说明

SpringBoot 版本 2.6.5

SpringCloud 版本 2021.0.1

SpringCloudAlibaba 版本 2021.0.1.0

读者可以先看笔者前面写的文章《Spring Cloud Gateway 使用 Redis 限流使用教程》,里面有创建项目的详细版本说明,这篇seata的文章是在 gateway 限流的项目基础上创建的

本文详细说明

数据库服务器版本 mysql 8.0.25

mybatis plus 版本 3.5.1

nacos 版本 1.4.2

seata 客户端版本 1.4.2

seata 服务端版本 1.7.1,笔者在文章最后面会使用服务端版本 1.4.2 演示,这里使用1.7.1版本的原因是1.4.2版本没有web控制台,且配置没有1.7.1方便,目前1.7.1版本是最新版

目录

1、创建项目

1.1、新建 maven 聚合项目 cloud-learn

1.2、创建 account 服务

1.3、创建 order 服务

2、添加配置

2.1、客户端配置

2.2、服务端配置

3、数据库建表

3.1、seata 服务端建表

3.2、seata 客户端建表

4、运行测试

5、Seata Server 1.4.2

6、项目代码


1、创建项目

1.1、新建 maven 聚合项目 cloud-learn

最外层父工程 cloud-learn 的 pom.xml

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wsjzzcbq</groupId>
    <artifactId>cloud-learn</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>gateway-learn</module>
        <module>consumer-learn</module>
        <module>sentinel-learn</module>
        <module>seata-at-account-learn</module>
        <module>seata-at-order-learn</module>
    </modules>
    <packaging>pom</packaging>

    <repositories>
        <repository>
            <id>naxus-aliyun</id>
            <name>naxus-aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

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

    <properties>
        <spring-cloud.version>2021.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
        <alibaba-nacos-discovery.veriosn>2021.1</alibaba-nacos-discovery.veriosn>
        <alibaba-nacos-config.version>2021.1</alibaba-nacos-config.version>
        <spring-cloud-starter-bootstrap.version>3.1.1</spring-cloud-starter-bootstrap.version>
        <druid.version>1.1.17</druid.version>
        <mysql.version>8.0.11</mysql.version>
        <mybatis-plus.version>3.5.1</mybatis-plus.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                <version>${alibaba-nacos-discovery.veriosn}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
                <version>${alibaba-nacos-config.version}</version>
            </dependency>

            <!--spring-cloud-dependencies 2020.0.0 版本不在默认加载bootstrap文件,如果需要加载bootstrap文件需要手动添加依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bootstrap</artifactId>
                <version>${spring-cloud-starter-bootstrap.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>2.0.40</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>


</project>

下面会创建2个服务 account 和 order,模拟用户下订单后扣减账户金额,服务间使用 feign 调用,因为 account 和 order 服务使用不同的数据库,因此产生分布式事物,使用 seata 解决

seata 默认使用 AT 事物模型,本文讲解演示的就是 AT 事物模型,其他事物模型在后面的文章中讲解

1.2、创建 account 服务

创建子工程 seata-at-account-learn

seata-at-account-learn pom 文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-learn</artifactId>
        <groupId>com.wsjzzcbq</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-at-account-learn</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

启动类 SeataATAccountApplication

package com.wsjzzcbq;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * SeataATAccountApplication
 *
 * @author wsjz
 * @date 2023/10/14
 */
@MapperScan(value = {"com.wsjzzcbq.mapper"})
@SpringBootApplication
public class SeataATAccountApplication {

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

实体类 Account

package com.wsjzzcbq.bean;

import lombok.Data;

/**
 * Account
 *
 * @author wsjz
 * @date 2022/07/07
 */
@Data
public class Account {

    private Integer id;

    private String userId;

    private Integer money;
}

 AccountMapper

package com.wsjzzcbq.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wsjzzcbq.bean.Account;

/**
 * AccountMapper
 *
 * @author wsjz
 * @date 2023/10/13
 */
public interface AccountMapper extends BaseMapper<Account> {
}

AccountService

package com.wsjzzcbq.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.wsjzzcbq.bean.Account;

/**
 * AccountService
 *
 * @author wsjz
 * @date 2023/10/13
 */
public interface AccountService extends IService<Account> {

    String reduce(String userId, int money);
}

AccountServiceImpl

package com.wsjzzcbq.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wsjzzcbq.bean.Account;
import com.wsjzzcbq.mapper.AccountMapper;
import com.wsjzzcbq.service.AccountService;
import io.seata.core.context.RootContext;
import org.springframework.stereotype.Service;

/**
 * AccountServiceImpl
 *
 * @author wsjz
 * @date 2023/10/13
 */
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {

    @Override
    public String reduce(String userId, int money) {
        String xid = RootContext.getXID();
        System.out.println(xid);
        UpdateWrapper<Account> up = new UpdateWrapper<>();
        String sql = "money = money - " + money;
        up.setSql(sql);
        up.eq("user_id", userId);
        this.update(up);
        return "ok";
    }
}

AccountController

package com.wsjzzcbq.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wsjzzcbq.bean.Account;
import com.wsjzzcbq.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * AccountController
 *
 * @author wsjz
 * @date 2023/10/13
 */
@RequestMapping("/account")
@RestController
public class AccountController {

    @Autowired
    private AccountService accountService;

    @GetMapping("/find")
    public String find() throws JsonProcessingException {
        Account account = accountService.list().get(0);
        ObjectMapper objectMapper = new ObjectMapper();
        String res = objectMapper.writeValueAsString(account);
        System.out.println(res);
        return res;
    }

    @RequestMapping("/reduce")
    public String debit(String userId, int money) {
        try {
            accountService.reduce(userId, money);
            return "扣款成功";
        } catch (Exception e) {
            return "扣款失败";
        }
    }
}

application.yml 文件

server:
  port: 9001
spring:
  application:
    name: seata-at-account-learn
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.3.232:3306/pmc-account?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  cloud:
    nacos:
      username: nacos
      password: nacos
      server-addr: 192.168.2.140
      discovery:
        namespace: public
#        server-addr: 192.168.2.140
#      config:
#        server-addr:



seata:
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.server-addr}
      username: ${spring.cloud.nacos.username}
      password: ${spring.cloud.nacos.password}
      group: SEATA_GROUP
      data-id: seata.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      cluster: default
      server-addr: ${spring.cloud.nacos.server-addr}
      username: ${spring.cloud.nacos.username}
      password: ${spring.cloud.nacos.password}
      group: SEATA_GROUP
# 事物分组,如果不配置默认是spring.application.name + '-seata-service-group'
#  tx-service-group:


logging:
  level:
    com.wsjzzcbq.mapper: debug

mybatis-plus:
  global-config:
    db-config:
      id-type: auto

关键配置说明

nacos 注册中心和配置中心默认从 spring.cloud.nacos.server-addr 中获取,因此可以配置一个

seata config 和 registry,config是客户端在nacos config 中存放的配置文件,它的 group 是 SEATA_GROUP,data-id 是 seata.properties,当然 group 和 data-id 名称是任意自定义的,但要保证和 nacos 中的对应上,否则找不到配置,seata.properties 具体配置内容后面详细说明;registry 配置的是nacos 中seata server 的信息,seata 客户端通过nacos 注册中心中配置的 seata server 的信息获取 seata server 实例,进行连接,这里笔者配置的 seata server group 是 SEATA_GROUP,seata server 的服务名是 seata-server,其实,可以把 seata server 理解为注册在nacos中的服务,相同的服务名,多个实例。项目启动后,会在nacos 注册中心中寻找服务名为 seata-server 的 seata 服务器,seata config、registry、nacos 和 seata server 的关系,看下图

tx-service-group 事物分组,在同一分布式事物中的服务,需要使用同一事物分组,事物分组如果不配置,默认是 spring.application.name + '-seata-service-group',这里笔者没有配置,使用默认的,即为 seata-at-account-learn-seata-service-group。事物分组是 seata的资源逻辑,事物分组详细说明,看官网文档截图

1.3、创建 order 服务

创建子工程 seata-at-order-learn 项目

seata-at-order-learn pom 文件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-learn</artifactId>
        <groupId>com.wsjzzcbq</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-at-order-learn</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

启动类 SeataATOrderApplication

package com.wsjzzcbq;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * SeataATOrderApplication
 *
 * @author wsjz
 * @date 2023/10/14
 */
@MapperScan(value = {"com.wsjzzcbq.mapper"})
@EnableFeignClients
@SpringBootApplication
public class SeataATOrderApplication {

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

订单实体类 Order

package com.wsjzzcbq.bean;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * Order
 *
 * @author wsjz
 * @date 2022/07/07
 */
@TableName("order_tbl")
@Data
public class Order {

    private Integer id;

    private String userId;

    private String code;

    private Integer count;

    private Integer money;
}

OrderMapper

package com.wsjzzcbq.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wsjzzcbq.bean.Order;

/**
 * OrderMapper
 *
 * @author wsjz
 * @date 2022/07/07
 */
public interface OrderMapper extends BaseMapper<Order> {
}

AccountFeign

package com.wsjzzcbq.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * AccountFeign
 *
 * @author wsjz
 * @date 2023/10/13
 */
@FeignClient(value = "seata-at-account-learn")
public interface AccountFeign {

    @RequestMapping("/account/reduce")
    String debit(@RequestParam("userId") String userId, @RequestParam("money") int money);
}

OrderService

package com.wsjzzcbq.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.wsjzzcbq.bean.Order;

/**
 * OrderService
 *
 * @author wsjz
 * @date 2022/07/07
 */
public interface OrderService extends IService<Order> {

    void create(String userId, int money, boolean rollback);
}

OrderServiceImpl

package com.wsjzzcbq.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wsjzzcbq.bean.Order;
import com.wsjzzcbq.feign.AccountFeign;
import com.wsjzzcbq.mapper.OrderMapper;
import com.wsjzzcbq.service.OrderService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;

/**
 * OrderServiceImpl
 *
 * @author wsjz
 * @date 2022/07/07
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    @Autowired
    private AccountFeign accountFeign;

    @GlobalTransactional
    @Override
    public void create(String userId, int money, boolean rollback) {
        String xid = RootContext.getXID();
        System.out.println(xid);
        String orderCode = UUID.randomUUID().toString();

        Order order = new Order();
        order.setCode(orderCode);
        order.setCount(1);
        order.setUserId(userId);
        order.setMoney(money);

        this.save(order);

        accountFeign.debit(userId, money);

        if (rollback) {
            int a = 1/0;
        }

    }
}

OrderController

package com.wsjzzcbq.controller;

import com.wsjzzcbq.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * OrderController
 *
 * @author wsjz
 * @date 2022/07/09
 */
@RequestMapping("/order")
@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * http://localhost:9002/order/create?userId=101&money=10&rollback=false
     * @param userId
     * @param money
     * @param rollback
     * @return
     */
    @RequestMapping("/create")
    public String create(String userId, int money, boolean rollback) {
        try {
            orderService.create(userId, money, rollback);
            return "下单成功";
        } catch (Exception e) {
            e.printStackTrace();
            return "下单失败";
        }
    }

}

application.yml 文件

server:
  port: 9002
spring:
  application:
    name: seata-at-order-learn
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.3.232:3306/pmc-order?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  cloud:
    nacos:
      username: nacos
      password: nacos
      server-addr: 192.168.2.140
      discovery:
        namespace: public
#        server-addr: 192.168.2.140
#      config:
#        server-addr:



seata:
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.server-addr}
      username: ${spring.cloud.nacos.username}
      password: ${spring.cloud.nacos.password}
      group: SEATA_GROUP
      data-id: seata.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      cluster: default
      server-addr: ${spring.cloud.nacos.server-addr}
      username: ${spring.cloud.nacos.username}
      password: ${spring.cloud.nacos.password}
      group: SEATA_GROUP
# 事物分组,如果不配置默认是spring.application.name + '-seata-service-group'
  tx-service-group: seata-at-account-learn-seata-service-group

logging:
  level:
    com.wsjzzcbq.mapper: debug

mybatis-plus:
  global-config:
    db-config:
      id-type: auto

配置说明

基本和 account 服务配置相同,这里事物分组和 account 服务是一样的 seata-at-account-learn-seata-service-group

2、添加配置

2.1、客户端配置

需要在nacos 中新建 group 是 SEATA_GROUP,data-id 是 seata.properties 的客户端配置

配置内容如何获取?可以在github 上克隆seata 代码,在源代码 script 目录下有 config-center 目录,在 config-center 目录下有全部配置在 config.txt 文件中

seata 源代码地址:https://github.com/seata/seata

另一种方式是下载seata server,笔者下载的 seata server 1.7.1解压后,有 script目录,script目录下config-center 下config.txt 文件中有全部配置

seata server 下载地址:https://github.com/seata/seata/releases

config.txt 文件中有英文注释,说明了哪些配置是客户端的哪些是服务端的

这里笔者已经整理好了客户端配置,在nacos上新建 group 是 SEATA_GROUP,data-id 是 seata.properties 的配置,内容如下

seata.properties 内容

#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

#Transaction routing rules configuration, only for the client
service.vgroupMapping.seata-at-account-learn-seata-service-group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false

#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
# You can choose from the following options: fastjson, jackson, gson
tcc.contextJsonParserType=fastjson

#Log rule configuration, for client and server
log.exceptionRate=100

这里需要改的有2处,一个是 service.vgroupMapping.seata-at-account-learn-seata-service-group=default,需要把 service.vgroupMapping. 后面的改成 account项目和 order 项目共同的事物分组 seata-at-account-learn-seata-service-group,这里笔者已经改完,默认的配置不是这个;另一处配置是 service.default.grouplist=127.0.0.1:8091,这里配置的是 seata server 的地址,因为笔者的 seata server 和项目在同一台电脑上,因此不做修改,使用127.0.0.1,读者可根据自己的情况配置

2.2、服务端配置

先下载 seata-server-1.7.1,然后进入 seata-server-1.7.1 的 conf 目录

在 application.yml 文件中进行配置

spring.application.name 默认是 seata-server ,和前面项目中配置的一样,不用修改

控制台账号密码默认都是 seata

seata config 和 registry 是关键,道理和客户端类似,seata server 从nacos 配置中心中获取group 是 SEATA_GROUP,data-id 是 seataServer.properties 的配置

同时会把自身以group 是 SEATA_GROUP,服务名是 seata-server 的形式注册到 nacos 注册中心

cluster 是 default,前面客户端的 service.vgroupMapping.seata-at-account-learn-seata-service-group=default,service.default.grouplist=127.0.0.1:8091,都是以 default 对应的

笔者的 seata-server-1.7.1 的 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: ${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:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
      server-addr: 192.168.2.140:8848
      namespace:
      group: SEATA_GROUP
      username: nacos
      password: nacos
      context-path:
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key:
      #secret-key:
      data-id: seataServer.properties    
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: 192.168.2.140:8848
      group: SEATA_GROUP
      namespace:
      cluster: default
      username: nacos
      password: nacos
      context-path:
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key:
      #secret-key:    
  #store:
    # support: file 、 db 、 redis
    #mode: file
#  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

配置完 seata-server-1.7.1 的 application.yml 文件后

在nacos 中新建配置 seataServer.properties 

seataServer.properties 内容

#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

#Log rule configuration, for client and server
log.exceptionRate=100

#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
store.publicKey=

#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100

#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.3.232:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
store.redis.mode=single
store.redis.type=pipeline
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100

#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=true
server.enableParallelHandleBranch=false

#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

这里的关键配置是存储 store,默认是 file 文件的形式

笔者使用 mysql 数据库 db的形式储存事物相关信息

需修改下面7项内容

store.mode=db
store.lock.mode=db
store.session.mode=db
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.3.232:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456

笔者使用 mysql8,因此使用 com.mysql.cj.jdbc.Driver,数据库信息读者根据自己的情况修改

nacos 中新建 seataServer.properties 

3、数据库建表

3.1、seata 服务端建表

笔者所有数据库使用同一数据库服务器

新建数据库 seata

建表 sql 在 seata-server-1.7.1 的 seata-server-1.7.1\seata\script\server\db 目录下

创建完成,有4张表

3.2、seata 客户端建表

seata 为实现分布式事物,业务库下需要有张记录日志的 undo_log 表

undo_log 表 sql 可以在seata源码 seata\script\client\at\db 目录下找到,不同版本 seata server 会有差异

笔者 account 服务建表,已包含 undo_log 表

DROP TABLE IF EXISTS `account`;
CREATE TABLE `account`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `money` int(0) NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES (1, '101', 900);

-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(0) 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(0) NOT NULL,
  `log_created` datetime(0) NOT NULL,
  `log_modified` datetime(0) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

笔者 order 服务建表,已包含 undo_log 表

DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `count` int(0) NULL DEFAULT 0,
  `money` int(0) NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(0) 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(0) NOT NULL,
  `log_created` datetime(0) NOT NULL,
  `log_modified` datetime(0) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

建表完成

4、运行测试

启动 seata-server-1.7.1

进入 bin 目录,双击 seata-server.bat

seata 控制台:http://localhost:7091/

账号密码都是 seata

启动 account 和 order 服务

nacos 服务和配置

测试正常情况

浏览器请求:http://localhost:9002/order/create?userId=101&money=10&rollback=false

扣减账户 10 元,新增订单

测试回滚情况

5、Seata Server 1.4.2

seata-server-1.4.2 配置说明

进入 seata-server-1.4.2 的 conf 目录

配置文件是 registry.conf 和 file.conf,这个是seata 服务端早期的配置方式,没有1.7.1的application.yml 文件方便

registry.conf 文件中 registry, 通过 type 指定注册中心,默认是 file,如果使用 nacos,要在下面nacos 配置的位置配置nacos的信息,其他注册中心同理

registry.conf 文件中 config,通过 type 指定,默认是file,如果使用nacos,需要在下面nacos配置位置配置nacos信息,其他配置中心同理

file.conf 配置说明(如果registry.conf 文件中 config使用 file,file.conf 配置才生效),通过mode 指定存储形式,默认是file,如果想使用db,需要在下面db配置处配置数据库信息

6、项目代码

码云地址:https://gitee.com/wsjzzcbq/csdn-blog/tree/master/cloud-learn

至此完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟世君子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值