1. 启动Seata Server - TC全局事务协调器
1.1 下载Seata Server
Seata Server 就是 TC,直接从官方仓库下载启动即可,下载地址:https://github.com/seata/seata/releases
1.2 修改配置文件
1.2.1 /conf/registry.conf
1.2.2 /conf/file.conf
1.2.3 /bin/seata-server.bat
双击seata-server.bat,启动Seata Server
查看eureka注册中心信息
2. order订单服务添加 Seata AT 事务
2.1 添加 seata 依赖
order-parent 的 pom.xml 文件中有一段注释掉的 seata 依赖,现在可以打开它
2.2 配置application.yml
spring:
cloud:
alibaba:
seata:
tx-service-group: order_tx_group
2.3 配置registry.conf-注册中心的地址
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "eureka"
nacos {
serverAddr = "localhost"
namespace = ""
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
# application = "default"
# weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
password = ""
cluster = "default"
timeout = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
2.4 配置file.conf - 事务组使用哪个配置中心
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = true
#thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThread-prefix = "NettyServerBizHandler"
shareBossWorker = false
clientSelectorThreadPrefix = "NettyClientSelector"
clientSelectorThreadSize = 1
clientWorkerThreadPrefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
bossThreadSize = 1
#auto default pin or 8
workerThreadSize = "default"
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
#transaction service group mapping
# order_tx_group 与 yml 中的 “tx-service-group: order_tx_group” 配置一致
# “seata-server” 与 TC 服务器的注册名一致
# 从eureka获取seata-server的地址,再向seata-server注册自己,设置group
vgroupMapping.order_tx_group = "seata-server"
#only support when registry.type=file, please don't set multiple addresses
order_tx_group.grouplist = "127.0.0.1:8091"
#degrade, current not support
enableDegrade = false
#disable seata
disableGlobalTransaction = false
}
client {
rm {
asyncCommitBufferLimit = 10000
lock {
retryInterval = 10
retryTimes = 30
retryPolicyBranchRollbackOnConflict = true
}
reportRetryCount = 5
tableMetaCheckEnable = false
reportSuccessEnable = false
}
tm {
commitRetryCount = 5
rollbackRetryCount = 5
}
undo {
dataValidation = true
logSerialization = "jackson"
logTable = "undo_log"
}
log {
exceptionRate = 100
}
}
2.5 创建 seata 数据源代理
2.5.1 编辑配置类
package cn.tedu.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class DsAutoConfiguration {
//创建原始数据源对象 druid, hikari
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
//使用hikari连接池yml中数据库地址需要更改: url改成jdbcUrl
// return new HikariDataSource;
//创建AT数据源代理对象
@Primary//首选对象
@Bean
public DataSource dataSource(DataSource dataSource){
return new DataSourceProxy(dataSource);
}
}
2.5.2 启动类排除默认数据源配置
package cn.tedu;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@MapperScan("cn.tedu.mapper")
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
2.6 启动测试
在OrderServiceImpl实现类添加注解,另外我们一步一步地添加全局事务并测试,这里先把 storage 和 account 调用注掉
@GlobalTransactional
@Transactional
package cn.tedu.service;
import cn.tedu.entity.Order;
import cn.tedu.feign.AccountClient;
import cn.tedu.feign.EasyIdClient;
import cn.tedu.feign.StorageClient;
import cn.tedu.mapper.OrderMapper;
import com.netflix.discovery.converters.Auto;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Random;
@Service
public class OrderServiceImpl implements OrderService{
@Autowired
private OrderMapper orderMapper;
@Autowired
private AccountClient accountClient;
@Autowired
private StorageClient storageClient;
@Autowired
private EasyIdClient easyIdClient;
@Override
@GlobalTransactional
@Transactional
public void create(Order order) {
// TODO: 从全局唯一id发号器获得id,这里暂时随机产生一个 orderId
// Long orderId = Long.valueOf(new Random().nextInt(Integer.MAX_VALUE));
// order.setId(orderId);
//调用全局唯一id发号器,获取一个唯一的id
String s = easyIdClient.nextId("order_business");
Long orderId = Long.valueOf(s);
orderMapper.create(order);
// TODO: 调用storage,修改库存
// storageClient.decrease(order.getProductId(), order.getCount());
// TODO: 调用account,修改账户余额
// accountClient.decrease(order.getUserId(), order.getMoney());
}
}
调用保存订单,地址:
http://localhost:8083/create?userId=2&productId=2&count=20&money=200
观察控制台,看到全局事务和订单的分支事务已经启动,并可以看到全局事务ID(XID)和分支事务ID(Branch ID):
观察数据库中新添加的订单数据:
2.7 测试出现异常,回滚的情况
实现类模拟异常,重启order
3. storage库存服务添加 Seata AT 事务
3.1 复制order配置文件
与order项目中添加的配置完全相同,请参考订单配置章节配置下面三个文件:
application.yml
registry.conf
file.conf
3.2 AT事务的数据源代理
3.3 添加@Transactional,启动分支事务
3.4 启动 storage 项目进行测试
把调用商品库存注释去掉了,现把注释打开:
调用保存订单,地址:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100
订单会调用库存,这两个服务会分别启动一个分支事务,两个分支事务一起组成一个全局事务:
查看控制台:
查看数据库:
4. account账户服务添加 Seata AT 事务
4.1 重复之前storage服务的配置服务
4.2 启动 account 项目进行测试
调用保存订单,地址:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100
订单会调用库存和账户,这三个服务会分别启动一个分支事务,三个分支事务一起组成一个全局事务:
查看控制台:
查看数据库:
5 总结
AT事务的特点:
- 全自动事务
- 对业务无侵入
- 适用于80%业务场景