Seata是一款开源的分布式事务解决方案,提供了三种模式:AT模式、TCC模式和Saga模式。下面我将分别介绍它们的概念和区别,并给出Java实现步骤。
AT模式(Automatic Transaction)是Seata最常用的一种模式,也是比较传统的分布式事务模式。它利用数据库的本地事务特性,实现了全局事务的管理和控制。
在AT模式中,Seata控制中心将全局事务信息存储在一个独立的事务存储中心,协调各个分支事务的执行。在分支事务中,通过加锁机制来避免并发修改数据的问题,从而保证了数据的一致性。
具体的实现步骤如下:
在Spring Boot应用中添加Seata的依赖和配置,包括配置全局事务组和事务存储中心等。
xml
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata.version}</version>
</dependency>
yaml
spring:
application:
name: order-service
cloud:
alibaba:
seata:
tx-service-group: my_tx_group
service:
vgroup-mapping:
order-service-group: default
group-id: order-service-group
config:
type: nacos
nacos:
server-addr: localhost:8848
namespace: dev
group: SEATA_GROUP
username: nacos
password: nacos
dataId: file.conf
在需要进行分布式事务的服务方法上添加@GlobalTransactional注解,指定全局事务的属性。
java
Copy code
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@GlobalTransactional
@Override
public void createOrder(Order order) {
orderMapper.insert(order);
}
}
在实际的应用中,可以使用注解的方式来管理分支事务的执行。当分支事务发生异常或者出现回滚操作时,Seata控制中心将自动协调全局事务的回滚操作,从而保证了数据的一致性。
在Seata的TCC模式中,每个分支事务需要实现try、confirm和cancel三个方法,分别对应于尝试性执行、确认性执行和撤销性执行。当主事务需要执行分布式事务时,会先调用分支事务的try方法,尝试执行分支事务,并预留必要的资源。如果分支事务的try执行成功,则主事务会继续执行其他分支事务的try方法,直到所有分支事务都执行成功为止。
当所有分支事务的try方法都执行成功后,主事务会调用分支事务的confirm方法,进行确认性执行。此时,分支事务将真正执行业务逻辑,并提交必要的数据和资源。如果某个分支事务的confirm方法执行失败,则整个分布式事务将被回滚,即调用所有分支事务的cancel方法进行撤销性执行。
TCC使用场景:
TCC模式适用于需要进行复杂业务逻辑处理的场景,比如转账、下单等涉及多个服务的业务。在这些场景下,TCC模式能够提供分布式事务的可靠性、一致性、可回滚性和可恢复性。
Java实现步骤:
1.定义TCC接口,包含try、confirm和cancel三个方法。
2.实现TCC接口,编写try、confirm和cancel三个方法的业务逻辑。
3.在业务代码中使用@Compensable注解标记TCC接口中的try方法,实现Seata的TCC模式。
4.使用Seata框架提供的全局事务管理器(GlobalTransaction),协调和控制分支事务的执行。
例如
当下单成功后,需要对用户账户进行扣款,对商品库存进行减少操作。这个过程需要保证原子性,因为如果只对用户账户扣款而未对商品库存进行操作,就会导致商品超卖。而如果只对商品库存进行操作而未对用户账户扣款,就会导致用户购买了商品但未成功付款。
在Seata的TCC模式下,我们可以将下单、扣款和减少商品库存拆分为三个分支事务,分别实现try、confirm和cancel三个方法。以下是一个简单的Java实现:
首先,我们定义订单服务的TCC接口:
public interface OrderTccService {
@TwoPhaseBusinessAction(name = "orderTccService", commitMethod = "confirmPlaceOrder", rollbackMethod = "cancelPlaceOrder")
boolean placeOrder(@BusinessActionContextParameter(paramName = "orderId") String orderId,
@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
boolean confirmPlaceOrder(BusinessActionContext context);
boolean cancelPlaceOrder(BusinessActionContext context);
}
其中,我们使用了Seata提供的注解@TwoPhaseBusinessAction来标注TCC接口中的方法。该注解包含两个属性,分别为name和commitMethod、rollbackMethod。其中,name表示该TCC接口的全局唯一名称,而commitMethod和rollbackMethod则分别表示TCC接口的confirm和cancel方法的名称。
接下来,我们实现该TCC接口:
@Service
public class OrderTccServiceImpl implements OrderTccService {
@Autowired
private OrderService orderService;
@Override
@Transactional
public boolean placeOrder(String orderId, String userId, BigDecimal amount) {
// 尝试性执行,创建订单
boolean result = orderService.createOrder(orderId, userId, amount);
if (!result) {
throw new RuntimeException("Failed to create order");
}
return true;
}
@Override
@Transactional
public boolean confirmPlaceOrder(BusinessActionContext context) {
// 确认性执行,将订单状态置为已支付
String orderId = (String) context.getActionContext("orderId");
return orderService.updateOrderStatus(orderId, OrderStatus.PAYED);
}
@Override
@Transactional
public boolean cancelPlaceOrder(BusinessActionContext context) {
// 撤销性执行,删除订单
String orderId = (String) context.getActionContext("orderId");
return orderService.deleteOrder(orderId);
}
}
在上述代码中,我们将TCC接口中的try、confirm、cancel方法分别实现为本地事务。在try方法中,我们创建订单;在confirm方法中,我们将订单状态置为已支付;在cancel方法中,我们删除订单。这些操作都是本地事务,Seata会根据当前事务的状态,自动调用相应的confirm或cancel方法,实现分布式事务的控制和协调。
最后,我们在业务代码中调用TCC接口的try方法:
public class OrderController {
@Autowired
private OrderTccService orderTccService;
public boolean placeOrder(String orderId, String userId, BigDecimal amount) {
try {
// 尝试性执行分布式事务
orderTccService.placeOrderTry(orderId, userId, amount);
// 提交分布式事务
GlobalTransactionContext.reload(RootContext.getXID()).commit();
return true;
} catch (Exception e) {
// 回滚分布式事务
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
return false;
}
}
}
在业务代码中,我们先调用orderTccService.placeOrderTry()方法进行尝试性执行分布式事务,如果没有抛出异常,则提交分布式事务,否则回滚分布式事务。这样,我们就可以实现TCC模式的分布式事务控制和协调。
如果在调用orderTccService.placeOrderTry()方法的过程中出现异常,那么我们需要执行以下流程:
由于orderTccService.placeOrderTry()方法已经执行了一些操作,需要执行orderTccService.cancelPlaceOrder()方法来撤销这些作。
在撤销操作完成后,需要抛出异常通知上层业务处理异常情况。
在业务代码中捕获到异常后,执行分布式事务的回滚操作。
下面是在业务代码中调用TCC接口的try方法,同时处理异常情况的示例代码:
public class OrderController {
@Autowired
private OrderTccService orderTccService;
public boolean placeOrder(String orderId, String userId, BigDecimal amount) {
try {
// 尝试性执行分布式事务
orderTccService.placeOrderTry(orderId, userId, amount);
// 提交分布式事务
GlobalTransactionContext.reload(RootContext.getXID()).commit();
return true;
} catch (Throwable e) {
// 执行cancel操作,撤销try操作的结果
orderTccService.cancelPlaceOrder(orderId, userId, amount);
// 抛出异常通知上层业务处理异常情况
throw e;
} finally {
// 释放分布式事务上下文资源
GlobalTransactionContext.reload(RootContext.getXID()).release();
}
}
}
此外分布式还有很多种方案,例如依靠mq的可靠性实现柔性事务补偿,提高并发量比seata高,具体案例可以参照谷粒商城设计;
对于同库,但是又很多中间件的数据操作的事务,可以使用本地事务+任务调度,这个要求比较苛刻,具体案例可以看黑马的学成在线
以上的方案都是适用于ap的也就是最终一致性,对于cp强一致性,可以使用seata的XA和引入zookeeper