Seata是阿里开源的分布式事务解决方案中间件,对业务侵入小,在应用中Seata整体事务逻辑基于两阶段提交的模型,核心概念包含三个角色:
AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。
TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。
在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。
分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
## transaction log store, only used in seata-server
store {
## store mode: file、db
// todo
mode = "db"
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "mysql"
password = "mysql"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
service {
# todo 交易服务组映射
#transaction service group mapping
vgroupMapping.my_test_tx_group = "cloud_tx_group"
#only support when registry.type=file, please don't set multiple addresses
#这里设置ip, 一般不用改
default.grouplist = "127.0.0.1:8091"
#disable seata
disableGlobalTransaction = false
}
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
group = "DEFAULT_GROUP"
username = "nacos"
password = "nacos"
}
}
新建seata数据库,根据server-db.conf文件初始化表
在各个客户端根据client-db.conf建表,结构如下
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.2.0</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 监控管理模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
server:
port: 9101
spring:
application:
name: seata-account
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_account?allowMultiQueries=true&characterEncoding=utf8&useUnicode=true
username: root
password: hwl123
druid:
initial-size: 10
min-idle: 10
max-active: 100
max-wait: 60000 # 配置获取连接等待超时的时间
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis: 30000 # 配置一个连接在池中最小生存的时间,单位是毫秒
cloud:
nacos:
discovery:
server-addr: localhost:8848
alibaba:
seata:
tx-service-group: cloud_tx_group # 这里和file.conf中的 vgroupMapping.my_test_tx_group 保持一致
management:
endpoints:
web:
exposure:
include: "*"
## transaction log store, only used in seata-server
store {
## store mode: file、db
mode = "db"
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "mysql"
password = "mysql"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
service {
# todo # 这里和file.conf中的 vgroupMapping.my_test_tx_group 保持一致
vgroupMapping.cloud_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
#这里设置ip, 一般不用改
default.grouplist = "127.0.0.1:8091"
#disable seata
disableGlobalTransaction = false
}
@RestController
@RequestMapping("/account")
public class AccountController {
@Resource
private AccountMapper accountMapper;
@GetMapping("/decrease")
public void decrease(Integer num) {
AccountDO accountDO = accountMapper.selectById(1);
accountDO.setAmount(accountDO.getAmount() - num);
if (accountDO.getAmount() < 0) {
throw new RuntimeException("account less than 0");
}
accountMapper.updateById(accountDO);
}
}
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@Slf4j
@Component
public class OrderManager {
@Resource
private StoreMapper storeMapper;
@Resource
private AccountService accountService;
@GlobalTransactional(rollbackFor = Exception.class)
public void order(Integer account, Integer store) {
StoreDO storeDO = storeMapper.selectById(1);
storeDO.setGoodNum(storeDO.getGoodNum() - store);
storeMapper.updateById(storeDO);
accountService.accountDecrease(account);
log.info("order end");
}
}
@RestController
@RequestMapping("/order")
public class OrderController {
@Resource
private OrderManager orderManager;
@GetMapping("/do")
public void order(Integer account, Integer store) {
orderManager.order(account, store);
}
}