分布式事务Seata的4种模式详解
Seata 是一个开源的分布式事务解决方案,它在微服务架构下提供了高性能和简单易用的分布式事务服务。Seata 的设计基于 AT、TCC、Saga 和 XA 事务模式,以满足不同场景下的分布式事务处理需求,今天的内容针对 Seata 来详细介绍一下。
1、四种事务模式介绍
1. AT 模式:这是一种无侵入的分布式事务解决方案。用户只需关注自己的业务 SQL,Seata 框架会自动生成事务的二阶段提交和回滚操作。在一阶段,Seata 会拦截业务 SQL,解析 SQL 语义,找到要更新的业务数据,并保存快照数据和行锁。二阶段如果是提交,Seata 只需清理数据;如果是回滚,则用快照数据还原业务数据 。
2. TCC 模式:TCC(Try-Confirm-Cancel)模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作。Try 阶段是资源的检测和预留;Confirm 阶段执行业务操作提交;Cancel 阶段是预留资源释放 。
3. Saga 模式:Saga 模式适用于长事务,由事件驱动,各个参与者之间异步执行。如果任何一个正向操作执行失败,Saga 模式会执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态 。
4. XA 模式:XA 模式是 Seata 支持的另一种分布式事务解决方案,它利用事务资源对 XA 协议的支持,以 XA 协议的机制来管理分支事务。XA 模式是两阶段提交协议,通过资源管理器和事务协调者来保证事务的原子性。
下面我们用使用案例的方式来分别介绍4种模式,让你更好的理解不同模式下的作用。
2、使用案例-AT模式
实现一个分布式事务的案例涉及到多个微服务之间的协同工作,以及与Seata服务器的交互。以下是使用Seata的AT模式的一个示例,包括订单服务、库存服务和账户服务的基本实现,跟 V 哥来一起看一下实现过程。
1. 添加依赖
首先,在每个微服务的pom.xml
文件中添加Seata的依赖:
<dependencies>
<!-- 其他依赖... -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
</dependencies>
2. 配置application.yml
在每个微服务中配置Seata相关参数:
server:
port: 8081 # 每个服务的端口不同
spring:
application:
name: your-service-name
datasource:
# 数据源配置,如url, username, password等
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: your-namespace
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: your-namespace
service:
vgroup-mapping:
your-service-group: default
# 其他配置...
3. 启动类配置
在每个微服务的启动类上添加@EnableAutoDataSourceProxy
注解,以开启Seata的数据源代理。
@SpringBootApplication
@EnableDiscoveryClient
@EnableAutoDataSourceProxy
public class YourServiceApplication {
public static void main(String[] args) {
SpringApplication.run(YourServiceApplication.class, args);
}
}
4. 业务代码实现
以下是每个服务中可能的业务代码实现:
订单服务
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper; // JPA或MyBatis的Mapper
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public void createOrder(Order order) {
// 创建订单
orderMapper.insert(order);
// 其他业务逻辑...
}
}
库存服务
@Service
public class StorageService {
@Resource
private StorageMapper storageMapper;
@GlobalTransactional(name = "deduct-inventory", rollbackFor = Exception.class)
public void deductInventory(Long productId, Integer count) {
// 扣减库存
storageMapper.deduct(productId, count);
// 其他业务逻辑...
}
}
账户服务
@Service
public class AccountService {
@Resource
private AccountMapper accountMapper;
@GlobalTransactional(name = "deduct-balance", rollbackFor = Exception.class)
public void deductBalance(Long userId, BigDecimal money) {
// 扣减账户余额
accountMapper.deduct(userId, money);
// 其他业务逻辑...
}
}
5. 数据访问层
Mapper接口和XML文件或注解根据实际使用的ORM框架进行编写,这里以MyBatis为例:
public interface OrderMapper {
void insert(Order order);
// 其他方法...
}
public interface StorageMapper {
void deduct(Long productId, Integer count);
// 其他方法...
}
public interface AccountMapper {
void deduct(Long userId, BigDecimal money);
// 其他方法...
}
6. 异常处理
在业务方法中,如果抛出异常,则Seata会自动进行全局事务回滚。
注意一下哈,我们忽略了服务之间的网络通信、数据库配置、事务协调者(TC)的部署和配置等因素,在实际生产中你需要考虑得更加周全一些。
3、使用案例 - TCC 模式
使用Seata的TCC(Try-Confirm-Cancel)模式,我们需要为每个分支事务实现三个方法:Try、Confirm 和 Cancel。以下是使用TCC模式的一个简化示例,包括定义TCC接口和实现类。
1. 定义TCC接口
首先定义一个TCC接口,声明Try、Confirm和Cancel方法:
public interface ITccTransaction {
/**
* 尝试执行业务操作,预留必需的业务资源
*/
boolean prepare();
/**
* 确认执行业务操作,正式提交Try阶段预留的业务资源
*/
void commit();
/**
* 取消执行业务操作,释放Try阶段预留的业务资源
*/
void cancel();
}
2. 实现TccAction
创建一个实现ITccTransaction
接口的类TccAction
,实现具体的业务逻辑:
public class TccAction implements ITccTransaction {
@Override
public boolean prepare() {
// 尝试执行业务操作,例如检查资源是否足够
// 返回true表示Try阶段成功,false表示失败
return true;
}
@Override
public void commit() {
// 确认执行业务操作,例如提交订单
// 此方法在所有Try操作成功后被调用
}
@Override
public void cancel() {
// 取消执行业务操作,例如回滚订单
// 此方法在Try阶段任一操作失败时被调用
}
}
3. 业务服务类使用TccAction
在业务服务类中,使用TccAction
来执行分布式事务:
@Service
public class BusinessService {
private final TccAction tccAction = new TccAction();
public void executeBusiness() {
// 执行TCC事务
try {
if (tccAction.prepare()) {
// Try阶段成功,执行Confirm
tccAction.commit();
} else {
// Try阶段失败,执行Cancel
tccAction.cancel();
}
} catch (Exception e) {
// 异常处理,可能需要调用Cancel
tccAction.cancel();
throw e;
}
}
}
4. 配置Seata TCC模式
在每个微服务的配置文件application.yml
中配置Seata TCC模式:
seata:
enabled: true
tcc:
# 配置TCC模式的事务管理器Bean名称
manager: tccAction
5. 注册TccAction到Spring容器
在Spring的配置类中注册TccAction
到Spring容器:
@Configuration
public class SeataTccConfig {
@Bean
public ITccTransaction tccAction() {
return new TccAction();
}
}
6. 使用注解启动全局事务
在业务方法上使用@GlobalTransactional
注解来启动全局事务:
@Service
public class BusinessService {
// ... 其他代码 ...
@GlobalTransactional
public void executeBusinessInGlobalTransaction() {
executeBusiness();
}
}
提醒一下哈,在实际部署时,还需要考虑服务之间的网络通信、数据库配置、事务协调者(TC)的部署和配置等因素。在TCC模式下,业务侵入性较强,需要为每个分支事务手动编写Try、Confirm和Cancel逻辑。
4. 使用案例 - Saga 模式
Seata的Saga模式是用于处理长事务的解决方案,适用于业务流程长且需要保证事务最终一致性的场景。Saga模式通过状态机引擎来实现服务的编排,允许定义一系列的正向操作(执行业务逻辑)和相应的补偿操作(回滚业务逻辑)。以下是使用Seata的Saga模式的一个简化示例:
1. 定义Saga事务接口
首先定义一个Saga事务接口,声明开始事务和结束事务的方法:
public interface ISagaService {
/**
* 开始执行Saga事务
*/
void startSaga();
/**
* 结束执行Saga事务
*/
void endSaga();
}
2. 实现SagaService
创建一个实现ISagaService
接口的类SagaService
,实现具体的业务逻辑:
public class SagaService implements ISagaService {
private final OrderService orderService;
private final StorageService storageService;
private final AccountService accountService;
@Autowired
public SagaService(OrderService orderService, StorageService storageService, AccountService accountService) {
this.orderService = orderService;
this.storageService = storageService;
this.accountService = accountService;
}
@Override
public void startSaga() {
// 执行订单服务
orderService.createOrder();
// 执行库存服务
storageService.deductInventory();
// 执行账户服务
accountService.deductBalance();
}
@Override
public void endSaga() {
// 这里可以放置Saga事务结束时需要执行的逻辑
}
}
3. 定义补偿方法
为每个服务定义补偿方法,以便在Saga事务失败时执行:
@Service
public class OrderService {
// ... 省略其他代码 ...
public void createOrder() {
// 创建订单逻辑
}
public void compensateCreateOrder() {
// 订单创建失败时的补偿逻辑
}
// ... 省略其他代码 ...
}
@Service
public class StorageService {
// ... 省略其他代码 ...
public void deductInventory() {
// 扣减库存逻辑
}
public void compensateDeductInventory() {
// 扣减库存失败时的补偿逻辑
}
// ... 省略其他代码 ...
}
@Service
public class AccountService {
// ... 省略其他代码 ...
public void deductBalance() {
// 扣减账户余额逻辑
}
public void compensateDeductBalance() {
// 扣减余额失败时的补偿逻辑
}
// ... 省略其他代码 ...
}
4. 配置Saga状态机
使用Seata提供的状态机定义Saga事务的状态图。这通常是一个JSON格式的配置文件,定义了状态图的节点和边,以及正向操作和补偿操作的方法名称。
{
"stateMachineDefinition": {
"_comment": "This is a saga state machine definition",
"id": "your-saga-id",
"states": [
{
"id": "orderServiceState",
"type": "regular",
"serviceName": "orderService",
"methods": {
"regular": "createOrder",
"compensate": "compensateCreateOrder"
}
},
{
"id": "storageServiceState",
"type": "regular",
"serviceName": "storageService",
"methods": {
"regular": "deductInventory",
"compensate": "compensateDeductInventory"
}
},
{
"id": "accountServiceState",
"type": "regular",
"serviceName": "accountService",
"methods": {
"regular": "deductBalance",
"compensate": "compensateDeductBalance"
}
}
],
"links": [
{
"from": "orderServiceState",
"to": "storageServiceState"
},
{
"from": "storageServiceState",
"to": "accountServiceState"
}
],
"end": "accountServiceState"
}
}
5. 使用注解启动全局事务
在SagaService
的startSaga
方法上使用@GlobalTransactional
注解来启动全局事务:
@Service
public class SagaService implements ISagaService {
// ... 其他代码 ...
@GlobalTransactional(name = "saga-transaction", rollbackFor = Exception.class)
@Override
public void startSaga() {
// Saga事务逻辑
}
}
Saga模式允许业务流程中的每个参与者提交本地事务,并通过事件驱动的异步执行来保证事务的最终一致性。
5、使用案例 - XA 模式
Seata的XA模式是Seata提供的对XA协议的支持,它允许使用具有XA能力的数据库来参与分布式事务。下面来看一下使用Seata的XA模式的一个示例:
1. 添加依赖
在微服务的pom.xml
文件中添加Seata的XA模式依赖:
<dependencies>
<!-- 其他依赖... -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<!-- 添加XA数据源依赖,以HikariCP为例 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.0</version>
</dependency>
</dependencies>
2. 配置application.yml
在微服务的application.yml
文件中配置Seata和XA数据源:
server:
port: 8081
spring:
application:
name: your-service-name
seata:
enabled: true
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: your-namespace
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: your-namespace
tx-service-group: your-tx-service-group
# XA数据源配置
xa:
datasource:
ds1:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/your_database1
username: your_username
password: your_password
ds2:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/your_database2
username: your_username
password: your_password
3. 配置XA数据源
创建一个配置类来配置XA数据源:
@Configuration
public class XaDataSourceConfig {
@Value("${xa.datasource.ds1}")
private String ds1;
@Value("${xa.datasource.ds2}")
private String ds2;
@Bean
public XADataSource dataSource1() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(ds1);
// 其他配置...
return new XADataSourceProxy(dataSource);
}
@Bean
public XADataSource dataSource2() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(ds2);
// 其他配置...
return new XADataSourceProxy(dataSource);
}
}
4. 使用XA数据源
在业务服务中使用XA数据源:
@Service
public class XaBusinessService {
@Autowired
private JdbcTemplate jdbcTemplate1;
@Autowired
private JdbcTemplate jdbcTemplate2;
@Transactional
public void performBusiness() {
jdbcTemplate1.execute("BEGIN");
// 执行数据库操作1
jdbcTemplate1.update("UPDATE account SET balance = balance - ? WHERE id = ?", 100, 1);
jdbcTemplate2.execute("BEGIN");
// 执行数据库操作2
jdbcTemplate2.update("UPDATE inventory SET quantity = quantity - ? WHERE product_id = ?", 10, 1);
// 如果所有操作都成功,则提交事务
jdbcTemplate1.commit();
jdbcTemplate2.commit();
}
}
XA模式通过两阶段提交(2PC)来保证事务的原子性,适用于需要强一致性的场景。
5、Seata 的核心组件
Seata 的核心组件主要包括以下三个部分:
1.Transaction Coordinator (TC) - 事务协调者
负责维护全局事务的运行状态,是全局事务的大脑。TC 负责协调和管理所有的全局与分支事务,驱动全局事务的提交或回滚。
- 角色定位:TC 在分布式事务中扮演着“协调者”的角色,它是全局事务的中心节点,负责协调和管理所有分支事务的生命周期。
- 主要职责:
- 维护全局事务和分支事务的状态信息。
- 驱动全局事务的提交或回滚操作。
- 收集所有分支事务的执行情况,并根据这些信息决定全局事务是提交还是回滚。
- 与各个 Resource Manager (RM) 通信,调度分支事务的提交或回滚。
- 工作机制:TC 通过生成全局唯一的 XID(Transaction ID)来标识每个全局事务,并在全局事务的两阶段提交过程中,负责与所有的 RM 进行通信和协调。
2. Resource Manager (RM) - 资源管理器
负责管理本地事务处理的资源。RM 与 TC 进行通信,执行 TC 指令来保证本地事务的一致性。RM 主要是指事务的参与者,例如数据库连接,它们向 TC 注册分支事务,并汇报本地事务的状态,接收 TC 的命令来驱动本地事务的提交或回滚。
- 角色定位:RM 是分布式事务中“资源的管理者”,它负责管理本地资源,如数据库连接、消息队列等。
- 主要职责:
- 管理本地事务的资源,例如数据库连接和事务。
- 向 TC 注册本地事务,并汇报本地事务的状态。
- 接收 TC 的指令来驱动本地事务的提交或回滚。
- 在两阶段提交的第一阶段,RM 负责准备本地事务,并在第二阶段根据 TC 的指令执行提交或回滚操作。
- 工作机制:RM 在全局事务中作为分支事务的参与者,负责具体的业务执行和资源锁定,确保本地事务的原子性和一致性。
3. Transaction Manager ™ - 事务管理器
负责全局事务的启动、提交和回滚。TM 是全局事务的发起方,控制全局事务的范围,与 TC 和 RM 进行交互,并根据 TC 维护的全局事务和分支事务状态,做出全局提交或回滚的决议。
- 角色定位:TM 是分布式事务的“发起者”,它负责定义全局事务的范围和边界。
- 主要职责:
- 启动、提交或回滚全局事务。
- 向 TC 发送全局事务的开始请求,并在业务逻辑完成后发起全局提交或回滚的决议。
- 维护与 TC 的通信,传递业务逻辑的执行结果和全局事务的决议。
- 工作机制:TM 在应用层面上定义了全局事务的开始和结束,它在业务逻辑开始时向 TC 注册全局事务,并在业务逻辑结束时根据执行结果向 TC 发起全局提交或回滚的请求。
最后
这些组件协同工作,通过二阶段提交协议来确保分布式事务的原子性和一致性。TC 作为中心节点,负责协调各个 RM 的行为,而 TM 则负责向 TC 发起全局事务的请求。RM 则负责具体的资源管理,如数据库连接,并根据 TC 的指令执行相应的操作。通过这些组件和相应的协议,Seata 能够提供分布式事务的管理和协调能力,帮助开发者简化分布式系统中的事务处理,并确保数据的一致性。关注威哥爱编程公号,一起在技术路上成长。
- 点赞
- 收藏
- 关注作者
评论(0)