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
最外层父工程 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
至此完