Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata 配置非常灵活,支持多种注册中心、配置来源(配置中心)和持久化方式。本文选择 eureka 作注册中心,本地文件作配置,用 MySQL 作持久化。
registry {
type = "eureka"
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "seata-server"
weight = "1"
}
}
config {
type = "file"
file {
name = "file.conf"
}
}
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"
## 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.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"
}
}
注册(file 、nacos 、eureka、redis、zk、consul、etcd3、sofa)、配置(nacos 、apollo、zk、consul、etcd3)、存储(file、db、redis)方式有很多种方式,解压包里 conf/ 下配置文件各种配置方式都列出来了,只需要指定使用的方式,再修改对应类型的配置就行了。
创建数据库执行 TC MySQL 脚本
执行 bin/ 下脚本 seata-server.sh/bat 启动 TC 服务。
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-seataartifactId>
<version>${spring-cloud-alibaba-seata.version}version>
dependency>
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "eureka"
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "seata-server"
weight = "1"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
type = "file"
file {
name = "file.conf"
}
}
service {
#transaction service group mapping
vgroupMapping.my_test_tx_group = "seata-server"
}
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
storageFeignClient.deduct(commodityCode, orderCount);
orderFeignClient.create(userId, commodityCode, orderCount);
if (!validData()) {
throw new RuntimeException("账户或库存不足,执行回滚");
}
}
Seata TCC 三部操作对应 prepare、commit、rollback。
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
@LocalTCC
public interface ITccService {
/**
* Prepare
*/
@TwoPhaseBusinessAction(name = "tccTest", commitMethod = "tccTestCommit", rollbackMethod = "tccTestRollback")
String tccTest(@BusinessActionContextParameter(paramName = "param1") String param1,
@BusinessActionContextParameter(paramName = "param2") boolean param2);
/**
* Commit
*/
boolean tccTestCommit(BusinessActionContext businessActionContext);
/**
* Rollback
*/
boolean tccTestRollback(BusinessActionContext businessActionContext);
}
@GlobalTransactional
@RequestMapping("/tcc/rollback")
public String purchaseRollback() {
iTccService.tccTest(ThreadLocalRandom.current().nextInt() + "", true);
return "全局事务提交";
}
增加事务控制表来解决,表主要包含全局事务id(transaction_id)、分支事务id(branch_id)、状态(state:1-已初始化、2-已提交、3-已回滚)。
try 未执行的情况下执行了 cancel,比如开启全局事务后执行 try 超时了,但事务已经开启,需要推进到终态,因此 TC 通知执行 cancel,从而形成空回滚。
解决方案:执行 try 时在事务控制表插入一条记录,标记为1-已初始化。cancel 时检查记录存在,正常回滚;如果不存在,执行空回滚。
cancel 优先 try 执行,同样是 try 超时,然后执行了空回滚后 try 开始执行。这种情况下应该让 try 不执行。
解决方案 :cancel、commit 的时候判断如果事务控制表没记录则插入对应状态的记录,try 执行前如果发现有记录则不执行。
TC 在通知参与者提交/回滚的时候,如果没收到反馈会重复通知。
解决方式:参与者处理完后修改事务控制表中的状态,处理前判断状态如果处理过,则不做任何处理。
Seata 官网
Seata 中文文档
GitHub - TC 服务 Releases
GitHub - 官方使用例子程序
GitHub - Spring Cloud 快速集成 Seata
GitHub - 本文代码地址