看seata官网吧
修改conf目录下的file.conf和registry.conf
registry 我这里采用的是nacos,所以值粘贴了naocs的内容
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = "" #可以不填写
cluster = "default"
username = ""#必填
password = ""#必填
}
}
file (事务日志存储,仅在seata-server中使用 ),我这里使用的db,所以值粘贴了db的内容
TC Server运行环境部署
我们先部署单机环境的 Seata TC Server,用于学习或测试,在生产环境中要部署集群环境;
因为TC需要进行全局事务和分支事务的记录,所以需要对应的存储,目前,TC有三种存储模式( store.mode ):
file模式:适合单机模式,全局事务会话信息在内存中读写,并持久化本地文件 root.data,性能较高;
db模式:适合集群模式,全局事务会话信息通过 db 共享,相对性能差点;
redis模式:解决db存储的性能问题;
这里选择db模式
store {
## store mode: file、db、redis
mode = "db"
## rsa decryption public key
publicKey = ""
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.cj.jdbc.Driver"
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true"
user = "root"
password = "root"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
-- 数据库名要与file.conf中设置数据库名保持一致
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;
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);
Windows双击 bat
Linux 运行 sh,需要传参数(官网上有)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qIie7Kxb-1642733971994)(assets/微信图片_20220121101439.png)]
现在有两个服务,一个是订单服务(orderServer) 和 库存服务(stockServer);由订单服务调用库存服务,分别往不同的两个库中插入数据。
他采用的是nacos当做配置中心来使用,我这里只用了nacos当做注册中,未使用配置中心
<--版本需要跟seata服务版本一致,我的是1.4.2-->
<properties>
<alibaba.seata.version>2.2.0.RELEASEalibaba.seata.version>
<seata.version>1.4.2seata.version>
properties>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-seataartifactId>
<version>${alibaba.seata.version}version>
<exclusions>
<exclusion>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
<version>${seata.version}version>
dependency>
需要使用seata的服务都需要配置,基本上都一样,只是这个tx-service-group
不同。这里只是order服务的
# order 服务的
seata:
enabled: true
# 事务协调器组(下文中resources下`file.conf`中`tx-service-group`保持一致)
tx-service-group: order-server-group
application-id: order-id
# 默认开启数据源代理
enable-auto-data-source-proxy: true
# 代理模式默认AT
data-source-proxy-mode: AT
在resources下创建file.conf
和registry.conf
registry.conf
跟seata服务中的一样,复制过来就可以了registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = ""
password = ""
}
}
file.conf
这个就不同需要注意的地方这个vgroupmapping
后面的内容跟上文application.yml文件中的tx-service-group
一致。"="
号后面的需要跟registry.conf
中nacos下面的cluster
相同
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 {
#这里注意,等号前后都是配置,前面是yml里配置的事务组,后面是register.conf里定义的seata-server
vgroupMapping.order-server-group = "default"
#only support when registry.type=file, please don't set multiple addresses
seata_tc_server.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
}
}
undo_log
表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';
在启动类上添加@EnableAutoDataSourceProxy
在调用方加上@GlobalTransactional
服务启动后seata中会有响应的显示
@Override
@GlobalTransactional
public void createOrder() {
System.out.println("事务id:"+RootContext.getXID());
//调用添加方法,往数据库插入一条数据
addOrder();
//远程调用stock服务
stockFeign.add();
}
@Override
@Transactional
public void addStock() throws TransactionException {
System.out.println("事务ID:"+ RootContext.getXID());
//往数据库插入一条数据
Stock stock = new Stock();
stock.setSkuId(1);
stock.setUserName("wsl");
baseMapper.insert(stock);
//模拟异常
//throw new RuntimeException("阿巴阿巴...");
}
事务执行结果为Committed
,查看两个数据库都已经添加成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W8GdvZfb-1642733972019)(assets/printle-result-image.png)]
@Override
@Transactional
public void addStock() throws TransactionException {
System.out.println("事务ID:"+ RootContext.getXID());
Stock stock = new Stock();
stock.setSkuId(1);
stock.setUserName("wsl");
baseMapper.insert(stock);
//模拟异常
throw new RuntimeException("阿巴阿巴...");
}
查询order服务居然发现事务状态是提交的,查看数据库发现order服务正常插入了数据,stock服务因抛出了异常未插入数据是正常的。
百度了一下,发现seata使用feign调用的时候,如果服务中有@ControllerAdvice,异常统一处理类,或者feign降级就会出现失效的情况,但异常处理也是很重要的。所以这里采用AOP的方式来进行手动回滚,在stock服务中添加了如下代码
@Component
@Aspect
public class RollBackAspect {
@Pointcut(value = "@annotation(org.springframework.transaction.annotation.Transactional)")
public void pointcut(){
}
@AfterThrowing(pointcut = "pointcut()",throwing = "e")
public void rollBack(Throwable e) throws TransactionException {
if(StringUtils.isNotEmpty(RootContext.getXID())) {
//回滚事务
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
System.out.println("事务回滚了");
}
}
}
这次就正常了,两边数据库都没有插入数据