分布式事务解决方案---Seata

1、 分布式事务

2、分布式事务常见解决方案

分布式事务的解决方案通常有以下几种:

两阶段提交(2PC):是一种经典的分布式事务解决方案,它将分布式事务划分为准备和提交两个阶段,通过协调者(Coordinator)和参与者(Participant)来实现分布式事务的一致性。但是,由于 2PC 的强一致性和阻塞特性,在高并发场景下性能不佳。

TCC(Try-Confirm-Cancel):是一种基于业务逻辑的分布式事务解决方案,通过事务的 try、confirm 和 cancel 阶段来实现分布式事务的一致性。TCC 的实现需要对业务逻辑进行拆分和重构,相对来说比较复杂。

消息队列:通过将分布式事务拆分为异步消息来实现分布式事务的一致性。具体实现方式是将分布式事务的各个子操作放入消息队列中,通过消息队列的事务性特性来保证分布式事务的一致性。消息队列的实现相对来说比较简单,但是对消息队列的性能和可靠性要求较高。

Seata:是一种新型的分布式事务解决方案,通过对分布式事务的划分、协调和补偿来实现分布式事务的一致性。Seata 支持多种事务模式和事务日志存储方式,可以适应各种不同的业务场景。

以上是常见的几种分布式事务解决方案,每种解决方案都有其优缺点和适用场景,具体选择哪种解决方案需要根据具体业务场景进行评估。
下面主要介绍一下常用的消息对了和Seata方式

3、消息队列

当使用消息队列作为分布式事务解决方案时,可以将一个分布式事务拆分成多个子操作,每个子操作都是一个独立的消息,然后将这些消息放入消息队列中。消息队列的事务性特性可以保证消息的可靠性,即只有当消息被成功消费后,才会从消息队列中移除,从而保证消息的不丢失和不重复消费。

例如,对于一个电商下单场景,可以将分布式事务拆分为以下几个子操作:
1、减少商品库存。
2、生成订单。
3、扣除用户账户余额。

针对这些子操作,可以将它们分别转化为消息,然后将这些消息放入消息队列中。对于每个子操作,可以使用消息队列提供的事务性特性来保证它们的一致性。如果有任何一个子操作失败,则可以通过回滚消息队列中已发送的消息来实现分布式事务的回滚。

通过使用消息队列作为分布式事务的解决方案,可以将分布式事务的锁定时间缩短,从而提高分布式事务的性能和吞吐量。同时,消息队列具有很好的可扩展性和可靠性,可以应对高并发场景下的各种挑战。

以下是一个基于RocketMQ实现的分布式事务示例,用于演示在电商下单场景中,如何将分布式事务拆分成多个子操作并将其转化为消息,然后将这些消息发送到RocketMQ中,最终通过RocketMQ的事务消息特性来实现分布式事务的一致性。

首先,我们需要定义一个RocketMQ事务监听器(TransactionListener),用于监听事务状态并实现事务的提交和回滚操作。下面是一个示例的RocketMQ事务监听器:

public class OrderTransactionListener implements TransactionListener {
    
    

    // 模拟商品库存和用户账户余额
    private Map<String, Integer> productInventory = new HashMap<>();
    private Map<String, Integer> userBalance = new HashMap<>();

    // 初始化商品库存和用户账户余额
    public OrderTransactionListener() {
    
    
        productInventory.put("product1", 100);
        productInventory.put("product2", 200);
        userBalance.put("user1", 1000);
        userBalance.put("user2", 2000);
    }

    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
    
    
        try {
    
    
            // 解析消息中的订单信息
            OrderInfo order = JSON.parseObject(new String((byte[])msg.getBody()), OrderInfo.class);
            // 减少商品库存
            reduceProductInventory(order.getProduct(), order.getQuantity());
            // 扣除用户账户余额
            reduceUserBalance(order.getUser(), order.getTotalPrice());
            // 订单生成成功,返回COMMIT_MESSAGE状态
            return LocalTransactionState.COMMIT_MESSAGE;
        } catch (Exception e) {
    
    
            // 出现异常,返回ROLLBACK_MESSAGE状态
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
    
    
        // 根据消息中的订单信息检查订单状态,如果订单已经生成,则返回COMMIT_MESSAGE状态,否则返回ROLLBACK_MESSAGE状态
        OrderInfo order = JSON.parseObject(new String(msg.getBody()), OrderInfo.class);
        if (order != null && order.getStatus() == OrderStatus.CREATED) {
    
    
            return LocalTransactionState.COMMIT_MESSAGE;
        } else {
    
    
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    // 减少商品库存
    private void reduceProductInventory(String productId, int quantity) {
    
    
        Integer inventory = productInventory.get(productId);
        if (inventory == null) {
    
    
            inventory = 0;
        }
        inventory -= quantity;
        productInventory.put(productId, inventory);
    }

    // 扣除用户账户余额
    private void reduceUserBalance(String userId, BigDecimal amount) {
    
    
        Integer balance = userBalance.get(userId);
        if (balance == null) {
    
    
            balance = 0;
        }
        balance -= amount.intValue();
        userBalance.put(userId, balance);
    }
}

以上代码中,我们定义了一个OrderTransactionListener类,实现了RocketMQ的事务监听器接口(TransactionListener)。在executeLocalTransaction方法中,我们将接收到的订单消息中的商品信息和用户信息存储到本地的商品库存和用户账户余额中,并在方法末尾返回LocalTransactionState.COMMIT_MESSAGE状态。在checkLocalTransaction方法中,我们检查订单状态是否为已生成,如果是,则返回LocalTransactionState.COMMIT_MESSAGE状态,否则返回LocalTransactionState.ROLLBACK_MESSAGE状态。

接下来,我们需要将订单生成操作转化为一个事务消息,并将其发送到RocketMQ中。下面是一个示例的订单生成服务(OrderService):

public class OrderService {
    
    

    private DefaultMQProducer producer;

    public OrderService() throws MQClientException {
    
    
        // 创建生产者实例,并设置事务监听器
        producer = new DefaultMQProducer("producerGroup");
        producer.setTransactionListener(new OrderTransactionListener());
        // 启动生产者实例
        producer.start();
    }

    public void createOrder(OrderInfo order) throws MQClientException, InterruptedException {
    
    
        // 创建订单消息
        Message message = new Message("orderTopic", "orderTag", JSON.toJSONBytes(order));
        // 发送事务消息
        TransactionSendResult result = producer.sendMessageInTransaction(message, null);
        // 根据事务消息发送结果,判断是否发送成功
        if (result.getLocalTransactionState() != LocalTransactionState.COMMIT_MESSAGE) {
    
    
            throw new RuntimeException("Failed to send transaction message");
        }
    }
}

以上代码中,我们定义了一个OrderService类,其中的createOrder方法接收一个订单信息对象,将其转化为一个订单消息并发送到RocketMQ中。发送时,我们调用了RocketMQ生产者实例的sendMessageInTransaction方法,并传入订单消息和事务执行参数(在本例中为null)。该方法将返回一个TransactionSendResult对象,其中包含了事务消息的发送结果和状态。如果发送结果为LocalTransactionState.COMMIT_MESSAGE,则表示事务消息发送成功;否则表示发送失败,我们在此抛出一个运行时异常。

通过以上示例,我们可以看到如何使用RocketMQ的事务消息特性来实现分布式事务的一致性。在此示例中,我们将订单生成操作拆分成减少商品库存和扣除用户账户余额两个子操作,然后将这些子操作转化为两个事务消息并发送到RocketMQ中,通过事务监听器的实现来保证分布式事务的一致性。当所有的子操作都执行成功时,RocketMQ将提交事务并将订单生成消息正式发送出去;否则,RocketMQ将回滚事务并撤销所有的子操作,以保证分布式事务的一致性。

4、Seata

以电商项目为例,更详细地介绍一下 Seata 在分布式事务管理中的作用。

在一个典型的电商项目中,可能包含以下微服务:

1、订单服务:负责处理用户下单、查询订单等操作。
2、库存服务:负责处理商品库存、扣减库存等操作。
3、支付服务:负责处理用户支付、查询支付状态等操作。

在这个项目中,下单操作需要涉及订单服务、库存服务和支付服务三个微服务的参与。下面是一个简单的流程:

1、用户下单请求到达订单服务,订单服务生成订单并调用库存服务扣减库存。
2、库存服务成功扣减库存后,调用支付服务进行支付。
3、支付服务处理完支付后,更新订单状态为已支付。

在这个流程中,需要协调三个微服务之间的事务操作,以保证数据的一致性和完整性。如果在其中一个服务出现异常的情况下,需要回滚所有微服务的操作,以保证数据的一致性和完整性。

使用 Seata 可以实现这种分布式事务的管理和协调。具体来说,可以在订单服务、库存服务和支付服务中嵌入 Seata 组件,通过 Seata 组件来协调各个微服务之间的事务操作。在上述例子中,可以使用 Seata 完成以下操作:

1、订单服务发起分布式事务:当用户下单请求到达订单服务时,订单服务使用 Seata 的 API 发起分布式事务,同时将库存服务和支付服务作为参与者加入到分布式事务中。
2、库存服务和支付服务注册为分支事务:当库存服务和支付服务收到订单服务的请求时,它们也使用 Seata 的 API 注册为分支事务,并将分支事务的信息返回给订单服务。
3、Seata 组件协调分布式事务:在分布式事务执行过程中,Seata 组件会协调各个参与者的事务操作。例如,如果库存服务出现异常,Seata 组件会回滚所有参与者的事务操作,以保证数据的一致性和完整性。

通过使用 Seata 来管理分布式事务,可以保证在分布式环境下的数据一致性和完整性。
代码如下:
1、添加 Seata 依赖
首先,在订单服务、库存服务和支付服务中添加 Seata 的依赖。例如,可以在 Maven 的依赖中添加以下内容:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>1.5.2</version>
</dependency>

2、配置 Seata 服务端
在使用 Seata 之前,需要先启动 Seata 服务端,以提供分布式事务协调服务。Seata 服务端需要配置存储模式、事务日志存储位置等参数。可以通过修改 Seata 的配置文件来配置服务端。例如,以下是一个简单的 Seata 配置文件:

service {
    
    
  vgroup_mapping.mall_order_tx_group = "default"  # 订单服务所在的事务分组
  vgroup_mapping.mall_inventory_tx_group = "default"  # 库存服务所在的事务分组
  vgroup_mapping.mall_payment_tx_group = "default"  # 支付服务所在的事务分组
}

store {
    
    
  mode = "db"  # 存储模式,可以是 file、db、redis 等
  db {
    
    
    driver = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "seata"
    password = "seata"
    ...
  }
}

config {
    
    
  ...
}

3、配置 Seata 客户端
在订单服务、库存服务和支付服务中,需要配置 Seata 客户端来连接 Seata 服务端。可以通过修改 Seata 的配置文件来配置客户端。例如,以下是一个简单的 Seata 配置文件:

service {
    
    
  vgroup_mapping.mall_order_tx_group = "default"  # 订单服务所在的事务分组
  vgroup_mapping.mall_inventory_tx_group = "default"  # 库存服务所在的事务分组
  vgroup_mapping.mall_payment_tx_group = "default"  # 支付服务所在的事务分组
}

client {
    
    
  ...
  rm {
    
    
    ...
  }
  tm {
    
    
    ...
  }
}

4、代码实现
在订单服务、库存服务和支付服务中,需要使用 Seata 的 API 来管理分布式事务。以下是一个简单的示例代码:

订单服务:

@Service
public class OrderService {
    
    
    @Resource
    private OrderMapper orderMapper;
    @Resource
    private InventoryService inventoryService;
    @Resource
    private PaymentService paymentService;

    @GlobalTransactional(name = "mall_order_tx_group", rollbackFor = Exception.class)
    public void createOrder(Order order) {
    
    
        // 扣减库存
        inventoryService.decreaseStock(order.getProductId(), order.getQuantity());
        // 创建订单
        orderMapper.insert(order);
        // 支付
        paymentService.pay(order.getUserId(), order.getAmount());
    }
}

库存服务:

@Service
public class InventoryService {
    
    
    @Resource
    private InventoryMapper inventoryMapper;
    
	@GlobalLock
	@Transactional(rollbackFor = Exception.class)
	public void decreaseStock(Long productId, Integer quantity) {
    
    
	    // 扣减库存
	    inventoryMapper.decreaseStock(productId, quantity);
	}
}

支付服务:

@Service
public class PaymentService {
    
    
    @Transactional(rollbackFor = Exception.class)
    public void pay(Long userId, BigDecimal amount) {
    
    
        // 扣减余额
        ...
    }
}

在订单服务中,使用 @GlobalTransactional 注解标记了整个创建订单的业务操作,该注解会开启 Seata 的全局事务。在该注解中,指定了事务分组名称为 “mall_order_tx_group”。在该方法中,先调用库存服务扣减库存,再创建订单,最后调用支付服务进行支付。由于使用了 @GlobalTransactional 注解,因此整个操作会被封装在一个分布式事务中。

在库存服务中,使用 @GlobalLock 注解标记了扣减库存的业务操作,该注解会开启 Seata 的分布式锁。在该方法中,使用了 @Transactional 注解,确保数据库事务的原子性。如果扣减库存的过程中出现异常,Seata 会通过分布式锁的机制防止并发问题。

在支付服务中,使用了 @Transactional 注解,确保扣减余额的操作具有数据库事务的原子性。

猜你喜欢

转载自blog.csdn.net/qq_40454136/article/details/129517461