seata解决分布式事务问题步骤(分布式环境下,方法异常后回滚策略)

Seata是一款开源的分布式事务解决方案,提供了三种模式:AT模式、TCC模式和Saga模式。下面我将分别介绍它们的概念和区别,并给出Java实现步骤。

AT模式

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控制中心将自动协调全局事务的回滚操作,从而保证了数据的一致性。

TCC模式:

在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

你可能感兴趣的:(java,java,数据库)