【商城实战(38)】Spring Boot:从本地事务到分布式事务,商城数据一致性的守护之旅

news2025/3/17 17:53:13

【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想深入钻研技术细节,还是探寻商城运营之道,本专栏都能提供从 0 到 1 的系统讲解,助力你打造独具竞争力的电商平台,开启电商实战之旅。

目录

  • 一、Spring Boot 事务管理机制探秘
    • 1.1 事务管理基础概念
    • 1.2 声明式事务管理
    • 1.3 事务传播行为
    • 1.4 事务隔离级别
    • 1.5 编程式事务管理
  • 二、分布式事务:概念与常见解决方案剖析
    • 2.1 分布式事务概念深度解读
    • 2.2 XA 协议解析
    • 2.3 TCC 模式详解
    • 2.4 XA 协议与 TCC 模式对比
  • 三、商城项目中的分布式事务实战应用
    • 3.1 商城业务场景中的分布式事务问题分析
    • 3.2 选择合适的分布式事务解决方案
    • 3.3 具体实现步骤与代码示例
    • 3.4 注意事项与常见问题解决
  • 四、总结与展望


一、Spring Boot 事务管理机制探秘

在商城项目的开发中,数据的一致性和完整性是至关重要的。想象一下,在电商购物场景里,用户下单购买商品,这一过程涉及到扣减商品库存、创建订单记录、更新用户账户余额等多个操作。如果这些操作不能保证全部成功执行,或者在执行过程中出现部分失败的情况,就可能导致数据不一致,比如商品库存扣减了,但订单却未成功创建,这对用户体验和商城运营都会造成极大的负面影响。而 Spring Boot 的事务管理机制正是解决这类问题的关键所在,它能够确保一系列数据库操作要么全部成功,要么全部失败,从而维护数据的可靠性。接下来,我们就深入探讨 Spring Boot 事务管理机制的各个方面。

1.1 事务管理基础概念

事务,从本质上来说,是一组不可分割的数据库操作集合,这些操作被视为一个整体,要么全部执行成功,要么全部回滚。事务具有 ACID 四大特性,这是确保事务可靠性的基石。原子性(Atomicity)保证事务中的所有操作要么都执行,要么都不执行,就像一个原子一样不可分割。比如在转账操作中,转出和转入这两个操作必须同时成功或者同时失败,否则就会出现资金不一致的情况。一致性(Consistency)要求事务执行前后,数据库的完整性约束不会被破坏,数据从一个正确状态转换到另一个正确状态。以商城库存管理为例,在商品出库和入库的事务中,库存总量应该保持不变,这就是一致性的体现。隔离性(Isolation)确保多个并发事务之间相互隔离,一个事务的执行不会被其他事务干扰,也不会影响其他事务的执行结果。在高并发的电商场景下,多个用户同时下单,如果没有隔离性,可能会出现库存超卖等问题。持久性(Durability)意味着一旦事务提交,其对数据库的修改就会永久保存,即使系统出现故障也不会丢失。Spring Boot 的事务管理正是严格遵循这些特性,通过底层的事务管理器与数据库进行交互,来保证事务的正确执行。

1.2 声明式事务管理

在 Spring Boot 中,使用@Transactional注解实现声明式事务管理是最为便捷和常用的方式。这种方式基于 AOP(面向切面编程),通过在方法或类上添加@Transactional注解,Spring 会自动在方法执行前后进行事务的开启、提交和回滚操作,开发者无需手动编写大量的事务控制代码,极大地提高了开发效率。比如,在商城的订单服务类中:

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;

    @Transactional
    public void createOrder(Order order) {
        // 保存订单信息
        orderRepository.save(order);
        // 执行其他业务逻辑
        // 如果发生异常,则事务会回滚
    }
}

在上述代码中,createOrder方法添加了@Transactional注解,当该方法被调用时,Spring 会自动开启一个事务。如果方法执行过程中没有发生异常,事务会在方法结束时自动提交;如果出现异常,事务会自动回滚,确保订单信息不会被错误地保存到数据库中,从而保证了数据的一致性。如果将@Transactional注解添加到类上,则该类中的所有公共方法都将具有事务支持。

1.3 事务传播行为

@Transactional注解中的propagation属性用于设置事务的传播行为,它定义了一个方法在调用另一个带有事务的方法时,事务应该如何进行传播。Spring Boot 提供了七种事务传播行为,每种行为都有其特定的应用场景。

  • Propagation.REQUIRED:这是默认的事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。在商城的业务逻辑中,大多数方法都可以使用这种传播行为。例如,在订单服务中,创建订单和更新订单状态的方法可能会调用其他服务的方法来完成相关操作,这些方法可以共享同一个事务,确保整个业务操作的原子性。
  • Propagation.REQUIRES_NEW:无论当前是否存在事务,都会创建一个新的事务,并将当前事务挂起。当新事务完成后,再恢复之前挂起的事务。在处理支付业务时,为了确保支付操作的独立性和安全性,可以使用REQUIRES_NEW传播行为。即使在支付过程中出现异常,也不会影响到之前的订单创建等事务。
  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务方式执行。这种传播行为适用于一些对事务要求不严格的查询操作,例如获取商品列表,当它被调用时,如果调用者已经在事务中,它就加入事务,否则就以非事务方式执行。
  • Propagation.MANDATORY:表示方法必须在一个已经存在的事务中执行,如果当前没有事务,则会抛出异常。在一些需要严格遵循事务上下文的场景中会用到,比如某些关键数据的更新操作,必须在事务中进行。
  • Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。对于一些不需要事务支持的操作,如记录日志等,可以使用这种传播行为,以提高性能。
  • Propagation.NEVER:表示方法绝对不能在事务范围内执行,如果当前存在事务,则抛出异常。在某些特殊的业务场景中,可能会有这种需求。
  • Propagation.NESTED:如果当前存在事务,则创建一个嵌套事务作为当前事务的子事务来运行;如果当前没有事务,则按REQUIRED属性执行。嵌套事务可以独立于外部事务提交或回滚,但外部事务回滚时,嵌套事务也会回滚。在一些复杂的业务逻辑中,需要对部分操作进行更细粒度的事务控制时,可以考虑使用这种传播行为。

1.4 事务隔离级别

@Transactional注解的isolation属性用于定义事务的隔离级别,它决定了一个事务与其他并发事务之间的隔离程度,从而控制并发事务可能导致的数据访问问题。常见的事务隔离级别有以下几种:

  • Isolation.DEFAULT:这是默认的隔离级别,表示使用底层数据库的默认隔离级别。对于大多数数据库而言,通常是Isolation.READ_COMMITTED。
  • Isolation.READ_UNCOMMITTED:允许事务读取其他事务未提交的数据,这种隔离级别可能会导致脏读、不可重复读和幻读等问题,在实际应用中很少使用。因为在这种级别下,一个事务可能会读取到另一个事务尚未提交的中间数据,这些数据可能会被回滚,从而导致数据的不一致性。
  • Isolation.READ_COMMITTED:只允许事务读取其他事务已经提交的数据,可以防止脏读,但仍然可能出现不可重复读和幻读。在商城的一些查询操作中,如果对数据的实时性要求不是特别高,可以使用这种隔离级别,以提高并发性能。例如,在查询商品列表时,可能会出现一个事务在多次查询期间,其他事务对商品数据进行了修改并提交,导致两次查询结果不一致,这就是不可重复读的情况。
  • Isolation.REPEATABLE_READ:保证在同一个事务中多次读取同一数据时,结果是一致的,能防止脏读和不可重复读,但可能会出现幻读。在处理一些对数据一致性要求较高的业务时,如订单处理,可能会使用这种隔离级别。例如,在一个事务中多次查询订单状态,不会因为其他事务对订单状态的修改而导致查询结果不一致。
  • Isolation.SERIALIZABLE:所有事务依次逐个执行,完全避免了脏读、不可重复读和幻读等问题,但这种隔离级别会严重影响并发性能,因为它会对事务进行串行化处理,在高并发场景下可能会导致性能瓶颈。在一些对数据一致性要求极高且并发量较低的场景中,可以考虑使用这种隔离级别。

1.5 编程式事务管理

除了声明式事务管理,Spring Boot 还支持编程式事务管理。编程式事务管理允许开发者通过代码显式地控制事务的边界,使用TransactionTemplate或直接使用底层的PlatformTransactionManager来实现。虽然声明式事务管理更为常用,但在某些特殊情况下,编程式事务管理提供了更灵活的控制方式。

使用TransactionTemplate的示例如下:

@Service
public class ProductService {
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private ProductRepository productRepository;

    public void updateProduct(Product product) {
        transactionTemplate.execute(status -> {
            try {
                // 执行更新操作
                productRepository.save(product);
                return true;
            } catch (Exception e) {
                // 回滚事务
                status.setRollbackOnly();
                return false;
            }
        });
    }
}

在上述代码中,TransactionTemplate的execute方法接受一个TransactionCallback接口的实现,在这个实现中可以编写具体的业务逻辑。通过status.setRollbackOnly()方法可以手动设置事务回滚。

使用PlatformTransactionManager的示例如下:

@Service
public class ProductService {
    @Autowired
    private PlatformTransactionManager transactionManager;
    @Autowired
    private ProductRepository productRepository;

    public void updateProduct(Product product) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            // 执行更新操作
            productRepository.save(product);
            transactionManager.commit(status);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(status);
            throw e;
        }
    }
}

这里通过PlatformTransactionManager获取事务状态TransactionStatus,并在业务逻辑执行成功时提交事务,出现异常时回滚事务。编程式事务管理在需要动态控制事务的场景中非常有用,例如根据不同的业务条件决定是否开启事务或者如何处理事务的回滚等。

二、分布式事务:概念与常见解决方案剖析

在商城项目逐渐走向分布式架构的过程中,分布式事务成为了确保系统数据一致性的关键难题。随着业务的不断扩展,一个业务操作往往会涉及多个服务和多个数据库的交互,比如在处理订单时,可能需要同时与库存服务、支付服务以及订单数据库、用户数据库进行通信和数据操作。在这种分布式环境下,如果不能妥善处理事务,就极易出现数据不一致的情况,严重影响商城的正常运营。因此,深入理解分布式事务的概念和常见解决方案,对于保障商城项目的稳定性和可靠性至关重要。

2.1 分布式事务概念深度解读

分布式事务,简单来说,是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。在分布式系统中,当一个事务跨越多个节点时,为了保持事务的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)这四个特性(即 ACID 特性),就需要分布式事务来协调各个节点的操作。以商城的订单创建为例,当用户下单时,订单服务需要在订单数据库中插入一条订单记录,同时库存服务需要在库存数据库中扣减相应商品的库存。这两个操作分属于不同的服务和数据库,必须作为一个整体来执行,要么都成功,要么都失败,否则就会出现订单已创建但库存未扣减,或者库存已扣减但订单未创建的不一致情况。分布式事务就是要确保在这样复杂的分布式环境下,各个节点的操作能够协同一致,保证数据的完整性和正确性。

2.2 XA 协议解析

XA 协议是一种经典的分布式事务解决方案,它由 X/Open 组织提出,规范了事务管理器(Transaction Manager,TM)与资源管理器(Resource Manager,RM)之间的通信接口 ,在 TM 与多个 RM 之间形成一个双向通信桥梁,从而在多个数据库资源下保证 ACID 四个特性。目前,像 Oracle、DB2、MySQL 等知名数据库都实现了 XA 接口,可作为 RM。XA 协议主要基于两阶段提交(2PC)和三阶段提交(3PC)来实现分布式事务。

  • 两阶段提交(2PC):2PC 是一个强一致、中心化的原子提交协议,包含协调者节点(Coordinator)和 N 个参与者节点(Participant) 。在第一阶段(准备阶段),协调者向所有参与者发送事务预处理请求(Prepare),参与者收到请求后执行事务操作,并将操作结果记录到本地日志中,但并不提交事务,然后向协调者反馈操作结果。如果所有参与者都反馈准备就绪,进入第二阶段(提交阶段),协调者向所有参与者发送提交请求,参与者收到后正式提交事务;如果有任何一个参与者反馈失败,协调者则向所有参与者发送回滚请求,参与者回滚事务。2PC 的优点是原理简单,实现方便,借助了数据库的提交和回滚操作,不侵入业务逻辑。然而,它也存在明显的缺点,比如单点故障问题,一旦协调者节点挂掉,会导致参与者收不到提交或回滚的通知,从而使参与者节点始终处于事务无法完成的中间状态;同步阻塞问题,在整个过程中,节点都处于阻塞状态,占用数据库资源,影响性能;还有可能出现数据不一致问题,在第二阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,就会导致节点间数据的不一致。
  • 三阶段提交(3PC):3PC 是在 2PC 的基础上进行改进,增加了 CanCommit 阶段,并引入了超时机制。在第一阶段(CanCommit 阶段),协调者向所有参与者询问是否可以完成本次事务,参与者检查自身状态后回复 Yes 或 No。如果所有参与者都回复 Yes,进入第二阶段(PreCommit 阶段),协调者向参与者发送 PreCommit 请求,参与者收到后开始执行事务操作,并将 Undo 和 Redo 信息记录到事务日志中,然后向协调者反馈 Ack 表示准备好提交,等待下一步指令;如果有参与者回复 No 或协调者等待超时,协调者向所有参与者发送 abort 请求,中断事务。第三阶段(DoCommit 阶段),如果所有参与者都能进行 PreCommit 提交,协调者向参与者发送 doCommit 请求,参与者执行事务提交操作并反馈 Ack;如果有参与者未完成 PreCommit 反馈或反馈超时,协调者向所有参与者发送 abort 请求,中断事务。3PC 的优点是解决了 2PC 中的同步阻塞问题,增加了参与者的超时机制,避免了参与者在长时间无法与协调者通讯时无法释放资源的情况;同时通过 CanCommit、PreCommit、DoCommit 三个阶段的设计,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的 。不过,3PC 仍然存在一些缺点,比如需要锁定资源,会降低系统性能;在最后提交阶段,如果协调者发出的中断命令,有参与者没有收到,参与者在超时后会自动提交事务,还是会造成数据不一致性。

2.3 TCC 模式详解

TCC(Try - Confirm - Cancel)模式是一种业务层面的分布式事务解决方案,属于柔性事务,最终一致性。它的工作原理是将一个事务分为三个阶段:

  • Try 阶段:完成所有业务检查(一致性),预留业务资源(准隔离性)。在这个阶段,主要是对业务进行预处理,检查相关业务条件是否满足,比如在扣减库存场景中,检查库存是否充足。如果检查通过,则预留相应的业务资源,如冻结库存数量,但是并不真正执行实际的业务操作,只是为后续的确认或取消操作做好准备。
  • Confirm 阶段:确认执行业务操作,不做任何业务检查,只使用 Try 阶段预留的业务资源 。当 Try 阶段所有参与者都成功完成后,进入 Confirm 阶段,此时会真正执行之前预留资源对应的业务操作,如真正扣减冻结的库存。这个阶段要求幂等性,即多次执行的结果和执行一次的结果是一样的,以防止因为重试等原因导致数据不一致。
  • Cancel 阶段:取消 Try 阶段预留的业务资源。如果在 Try 阶段或 Confirm 阶段出现异常,就会进入 Cancel 阶段,该阶段会撤销之前在 Try 阶段预留的业务资源,如解冻之前冻结的库存,使业务状态恢复到事务开始之前的状态。

以电商商城的订单支付业务为例,在 Try 阶段,订单服务会检查订单状态是否正常、用户账户余额是否足够等,并冻结相应的支付金额;Confirm 阶段,当支付成功通知到达时,订单服务确认支付操作,将冻结金额扣除并完成订单状态更新等操作;如果支付过程中出现异常,如支付超时或支付失败,进入 Cancel 阶段,订单服务取消之前冻结的支付金额,恢复用户账户余额。TCC 模式适用于对一致性要求不是特别高,但对性能和并发要求较高的场景,比如电商的下单、支付等高频业务场景,通过业务逻辑的补偿机制来保证最终的数据一致性,减少了对资源的长时间锁定,提高了系统的并发处理能力。

2.4 XA 协议与 TCC 模式对比

  • 一致性方面:XA 协议追求的是强一致性,通过两阶段或三阶段提交,确保在事务执行过程中所有参与节点的数据操作要么全部成功提交,要么全部回滚,在整个事务过程中严格遵循 ACID 特性 。而 TCC 模式是最终一致性,在事务执行过程中,允许存在短暂的数据不一致状态,通过后续的 Confirm 和 Cancel 操作来保证最终数据的一致性。
  • 性能方面:XA 协议在执行过程中,从准备阶段就开始锁定资源,直到整个事务完成才释放资源,长时间的资源锁定会导致系统性能下降,尤其在高并发场景下,锁竞争会更加激烈。TCC 模式由于在 Try 阶段只是预留资源,而不是直接锁定资源,并且在 Confirm 阶段快速完成实际业务操作,减少了资源锁定时间,在高并发场景下具有更好的性能表现,能够提高系统的吞吐量和响应速度。
  • 代码侵入性方面:XA 协议的两阶段提交内部过程对开发者屏蔽,开发者从代码层面基本感知不到这个过程,只需遵循 XA 协议的规范配置事务管理器和资源管理器即可 。而 TCC 模式需要开发者手动编写 Try、Confirm 和 Cancel 三个方法的业务逻辑,代码侵入性强,对业务开发的要求较高,需要开发者深入理解业务并精心设计这三个方法来确保事务的正确执行。
  • 适用场景方面:XA 协议适用于对数据一致性要求极高,业务操作涉及多个数据库且并发量不是特别大的场景,比如金融领域的核心账务处理等场景 。TCC 模式适用于电商、互联网等对并发性能要求高,业务逻辑相对灵活,允许一定时间内数据存在不一致的场景,例如电商的订单处理、库存管理等业务。

三、商城项目中的分布式事务实战应用

在商城项目中,分布式事务的处理是确保系统稳定运行和数据一致性的关键环节。随着商城业务的不断拓展和分布式架构的广泛应用,跨服务的业务操作变得愈发频繁,这就使得分布式事务的管理变得尤为重要。下面我们将深入探讨在商城项目中如何应用分布式事务处理,以保证跨服务业务操作的数据一致性。

3.1 商城业务场景中的分布式事务问题分析

以商城下单这一核心业务场景为例,用户下单过程涉及多个关键服务之间的协同操作,包括库存服务、订单服务和支付服务等。当用户确认下单后,订单服务需要创建订单记录,库存服务需要扣减商品库存,支付服务需要处理支付流程。在传统的单体架构中,这些操作可以在同一个事务中完成,通过本地事务的原子性来保证数据的一致性。然而,在分布式架构下,每个服务都拥有独立的数据库和事务管理机制,这就使得跨服务的事务协调变得复杂起来。

假设在下单过程中,库存服务成功扣减了商品库存,但由于网络波动或其他原因,订单服务在创建订单记录时失败,此时就会出现数据不一致的问题。用户的账户余额可能已经被扣除,但却没有生成有效的订单,同时库存也已经减少,这不仅会给用户带来极差的购物体验,还会给商城的运营带来潜在的损失。另外,在支付环节,如果支付服务处理成功,但订单服务和库存服务出现故障,同样会导致数据不一致,可能出现用户支付成功但未下单成功,或者商品库存未扣减的情况。这些问题严重影响了商城系统的稳定性和可靠性,因此,有效的分布式事务处理方案是解决这些问题的关键。

3.2 选择合适的分布式事务解决方案

在商城项目中,选择合适的分布式事务解决方案需要综合考虑业务特点、性能需求、系统复杂度等多方面因素。对于 XA 协议和 TCC 模式这两种常见的解决方案,我们可以从以下几个角度进行分析和选择。

XA 协议由于其强一致性的特点,适用于对数据一致性要求极高,业务操作涉及多个数据库且并发量不是特别大的场景。在商城的一些核心账务处理、重要订单数据的修改等场景中,如果数据的准确性和一致性至关重要,不容许出现任何数据不一致的情况,那么 XA 协议是一个不错的选择。例如,在处理用户的退款操作时,涉及到用户账户余额的增加和商城资金账户的减少,这两个操作必须保证原子性和一致性,使用 XA 协议可以确保在分布式环境下,这两个操作要么都成功,要么都失败,从而保证账务数据的准确无误。

TCC 模式则更适用于电商、互联网等对并发性能要求高,业务逻辑相对灵活,允许一定时间内数据存在不一致的场景。在商城的下单、支付等高频业务场景中,业务操作的响应速度和并发处理能力是关键因素。TCC 模式通过在业务层面实现事务的控制,在 Try 阶段快速进行业务检查和资源预留,避免了长时间的资源锁定,然后在 Confirm 阶段和 Cancel 阶段根据业务结果进行相应的操作,保证最终的数据一致性。例如,在用户下单时,使用 TCC 模式可以在 Try 阶段快速检查库存是否充足并冻结库存,然后在 Confirm 阶段根据支付结果进行真正的库存扣减和订单创建,大大提高了系统的并发处理能力和响应速度。

3.3 具体实现步骤与代码示例

下面以 TCC 模式为例,展示在商城项目中实现分布式事务处理的步骤和关键代码。

首先,定义 TCC 模式的接口,包括 Try、Confirm 和 Cancel 方法。以订单服务和库存服务为例:

// 订单服务TCC接口
public interface OrderTccService {
    // Try阶段:检查订单信息,预留订单资源
    boolean tryCreateOrder(Order order);
    // Confirm阶段:确认创建订单
    boolean confirmCreateOrder(Order order);
    // Cancel阶段:取消订单创建,释放预留资源
    boolean cancelCreateOrder(Order order);
}

// 库存服务TCC接口
public interface StockTccService {
    // Try阶段:检查库存并冻结库存
    boolean tryDeductStock(String productId, int quantity);
    // Confirm阶段:确认扣减库存
    boolean confirmDeductStock(String productId, int quantity);
    // Cancel阶段:解冻库存
    boolean cancelDeductStock(String productId, int quantity);
}

然后,实现 TCC 接口。在订单服务实现类中:

@Service
public class OrderTccServiceImpl implements OrderTccService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private StockTccService stockTccService;

    @Override
    public boolean tryCreateOrder(Order order) {
        // 检查订单信息,如用户信息、商品信息等
        if (order == null || order.getUserId() == null || order.getProductId() == null) {
            return false;
        }
        // 预留订单资源,例如生成订单号等
        order.setOrderNo(generateOrderNo());
        // 这里可以将订单信息先保存到临时表,标记为未确认状态
        orderRepository.saveTempOrder(order);
        // 调用库存服务的Try方法冻结库存
        return stockTccService.tryDeductStock(order.getProductId(), order.getQuantity());
    }

    @Override
    public boolean confirmCreateOrder(Order order) {
        // 将临时订单信息保存到正式订单表
        orderRepository.saveOrder(order);
        // 调用库存服务的Confirm方法确认扣减库存
        return stockTccService.confirmDeductStock(order.getProductId(), order.getQuantity());
    }

    @Override
    public boolean cancelCreateOrder(Order order) {
        // 删除临时订单信息
        orderRepository.deleteTempOrder(order.getOrderNo());
        // 调用库存服务的Cancel方法解冻库存
        return stockTccService.cancelDeductStock(order.getProductId(), order.getQuantity());
    }

    private String generateOrderNo() {
        // 生成订单号的逻辑,例如使用时间戳+随机数等
        return System.currentTimeMillis() + "" + new Random().nextInt(10000);
    }
}

在库存服务实现类中:

@Service
public class StockTccServiceImpl implements StockTccService {
    @Autowired
    private StockRepository stockRepository;

    @Override
    public boolean tryDeductStock(String productId, int quantity) {
        Stock stock = stockRepository.findByProductId(productId);
        if (stock == null || stock.getQuantity() < quantity) {
            return false;
        }
        // 冻结库存,例如更新库存表的冻结字段
        stock.setFrozenQuantity(stock.getFrozenQuantity() + quantity);
        stockRepository.updateStock(stock);
        return true;
    }

    @Override
    public boolean confirmDeductStock(String productId, int quantity) {
        Stock stock = stockRepository.findByProductId(productId);
        if (stock == null || stock.getFrozenQuantity() < quantity) {
            return false;
        }
        // 真正扣减库存
        stock.setQuantity(stock.getQuantity() - quantity);
        stock.setFrozenQuantity(stock.getFrozenQuantity() - quantity);
        stockRepository.updateStock(stock);
        return true;
    }

    @Override
    public boolean cancelDeductStock(String productId, int quantity) {
        Stock stock = stockRepository.findByProductId(productId);
        if (stock == null || stock.getFrozenQuantity() < quantity) {
            return false;
        }
        // 解冻库存
        stock.setFrozenQuantity(stock.getFrozenQuantity() - quantity);
        stockRepository.updateStock(stock);
        return true;
    }
}

最后,在业务逻辑中调用 TCC 接口来实现分布式事务:

@Service
public class OrderBusinessService {
    @Autowired
    private OrderTccService orderTccService;

    public void placeOrder(Order order) {
        if (orderTccService.tryCreateOrder(order)) {
            try {
                // 模拟支付成功
                boolean paymentSuccess = true;
                if (paymentSuccess) {
                    orderTccService.confirmCreateOrder(order);
                } else {
                    orderTccService.cancelCreateOrder(order);
                }
            } catch (Exception e) {
                orderTccService.cancelCreateOrder(order);
            }
        }
    }
}

3.4 注意事项与常见问题解决

在实现分布式事务处理时,有几个关键的注意事项和常见问题需要特别关注。

  • 幂等性处理:在 TCC 模式的 Confirm 和 Cancel 阶段,方法必须具有幂等性,即多次调用的结果和调用一次的结果是一样的。这是因为在分布式环境中,由于网络波动等原因,可能会导致方法被重复调用。为了实现幂等性,可以使用数据库的唯一约束、状态机等方式。例如,在订单服务的 Confirm 方法中,在保存订单信息时,可以使用订单号作为唯一约束,当重复调用时,由于订单号已经存在,数据库会自动拒绝插入重复数据,从而保证幂等性。
  • 异常处理:在分布式事务的执行过程中,可能会出现各种异常情况,如网络异常、服务超时等。对于这些异常,需要进行合理的处理,以确保事务的最终一致性。在 TCC 模式中,当 Try 阶段出现异常时,需要回滚已经执行的操作;当 Confirm 阶段出现异常时,需要进行重试或者进行补偿操作;当 Cancel 阶段出现异常时,同样需要进行重试或者记录异常信息,以便后续人工处理。例如,在订单服务的 Try 阶段,如果调用库存服务的 Try 方法失败,需要删除已经保存的临时订单信息,并回滚库存服务已经冻结的库存。
  • 事务协调与监控:分布式事务涉及多个服务之间的协调,因此需要建立有效的事务协调机制和监控体系。可以使用分布式事务框架(如 Seata 等)来实现事务的协调和管理,同时通过日志记录、监控系统等方式对事务的执行过程进行实时监控,及时发现和解决问题。例如,通过 Seata 的事务协调器(TC)可以实时监控全局事务和各个分支事务的状态,当出现异常时,可以及时进行事务的回滚或者重试操作。
  • 数据一致性验证:在分布式事务处理完成后,需要对数据的一致性进行验证,确保各个服务之间的数据状态是一致的。可以定期进行数据的对账操作,通过比对不同服务中的数据记录,检查是否存在数据不一致的情况。例如,定期对订单服务和库存服务中的数据进行对账,检查订单数量和库存数量是否匹配,如果发现不一致,及时进行数据修复。通过以上注意事项的关注和常见问题的解决,可以有效地提高分布式事务处理的可靠性和稳定性,确保商城项目的正常运行 。

四、总结与展望

Spring Boot 事务管理机制通过@Transactional注解等方式,为开发者提供了便捷且强大的本地事务控制能力,确保了在单体应用场景下数据操作的原子性、一致性、隔离性和持久性。无论是声明式事务管理还是编程式事务管理,都能满足不同业务场景下对事务控制的需求。而分布式事务处理,作为分布式架构下保障数据一致性的关键技术,XA 协议和 TCC 模式等解决方案各有优劣,在商城项目中,根据业务的实际需求和特点选择合适的方案,能够有效地解决跨服务业务操作中的数据一致性问题。

展望未来,随着分布式系统的不断发展和业务复杂度的持续增加,分布式事务技术也将不断演进。一方面,现有的分布式事务解决方案会不断优化和完善,比如提高 XA 协议的性能和容错性,进一步降低 TCC 模式的代码侵入性等。另一方面,新的分布式事务技术和框架可能会应运而生,以更好地适应云原生、微服务等新兴架构的发展趋势。同时,人工智能和大数据技术也可能会与分布式事务处理相结合,通过智能算法来优化事务的调度和处理,提高系统的整体性能和可靠性。对于商城项目开发者来说,持续关注分布式事务技术的发展动态,不断学习和应用新的技术,将是保障商城系统稳定运行和数据一致性的重要途径。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2316745.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

perl的package中“Subroutine new redefined”问题

我在一个脚本run_PMseq.V8.pl调用了一些.pm文件 $perl -c run_PMseq.V8.pl Subroutine new redefined at /mnt/lustre/user/wubin/01.Program/Scripts/01.script/GeneLab/PMSeq/package_V3/Add_mismatch.pm line 25. Subroutine generate_shell redefined at /mnt/lustre/use…

英语学习(GitHub学到的分享)

【英语语法&#xff1a;https://github.com/hzpt-inet-club/english-note】 【离谱的英语学习指南&#xff1a;https://github.com/byoungd/English-level-up-tips/tree/master】 【很喜欢文中的一句话&#xff1a;如果我轻轻松松的学习&#xff0c;生活的幸福指数会提高很多…

【eNSP实战】三层交换机使用ACL实现网络安全

拓图 要求&#xff1a; vlan1可以访问Internetvlan2和vlan3不能访问Internet和vlan1vlan2和vlan3之间可以互相访问PC配置如图所示&#xff0c;这里不展示 LSW1接口vlan配置 vlan batch 10 20 30 # interface Vlanif1ip address 192.168.40.2 255.255.255.0 # interface Vla…

Javascript BOM,DOM 知识简介

JSON 一种数据交换格式,作为数据载体,传输数据, Json比xml 更简单,可读性更高.js的对象和Json可以相互转换. //json定义格式: var varName{"key1":value1,"key2":value2};value的数据类型为数字,字符串(在双引号中),布尔值,数组(在方括号中),对象(在花括…

拆解 “ES 已死“ 伪命题:Agentic RAG 时代搜索引擎的终极形态

作者&#xff1a;来自 Elastic 李捷 xxx&#xff1a;“ES已死&#xff0c;#%#……” 我&#xff1a;&#xff1f;&#xff1f;&#xff1f; 最近&#xff0c;某厂商发了一堆公关文章&#xff0c;翻来覆去地炒作 “ES 已死”&#xff0c;“放弃 ES”。这哪是什么正经的技术文章&…

.net 6程序在IIS中部署后点击IIS设置报错“执行此操作时出错”

.net 6写的程序&#xff0c;需要在Windows服务器的IIS中部署&#xff0c;由于是刚装的系统&#xff0c;先安装.net 6运行时&#xff0c;装了才发现没有IIS&#xff0c;于是又通过“添加角色和功能”添加与IIS相关的功能。安装完毕后&#xff0c;在IIS中添加网站&#xff0c;并将…

《从零手写Linux Shell:详解进程控制、环境变量与内建命令实现 --- 持续更新》

承接上文Linux 进程的创建、终止、等待与程序替换保姆级讲解-CSDN博客&#xff0c;涉及所用到的代码&#xff0c;本文所绑定的资源就是上篇文章的主要代码。 完整代码在文章末尾 目录 1.实现编写代码输出一个命令行 a.如何获取自己的用户名&#xff0c;主机名&#xff0c;路径…

k8s环境部署

四台机器 分别是 k8s-master&#xff1a;172.25.254.100 k8s-node1&#xff1a;172.25.254.10 k8s-node2&#xff1a;172.25.254.20 docker-harbor&#xff1a;172.25.254.200 reg.timinglee.org 四台机器分别配置好网络和软件仓库 做好地址解析 scp -r /etc/hosts/ root17…

CentOS 系统安装 docker 以及常用插件

博主用的的是WindTerm软件链接的服务器&#xff0c;因为好用 1.链接上服务器登入后&#xff0c;在/root/目录下 2.执行以下命令安装docker sudo yum install -y yum-utilssudo yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.reposudo…

谷歌云服务器:服务器怎么安装???

谷歌云服务器&#xff1a;服务器怎么安装&#xff1f;&#xff1f;&#xff1f; 以下是详细分步指南&#xff0c;帮助你在 Google Cloud Platform (GCP) 上快速创建并配置云服务器&#xff08;Compute Engine 实例&#xff09;&#xff0c;并安装所需环境&#xff1a; 一、准备…

Redis--Zset类型

目录 一、引言 二、介绍 三、命令 1.zadd 2.zrange&#xff0c;zrevrange&#xff0c;zrangebyscore 3.zcard&#xff0c;zcount 4.zpopmax&#xff0c;bzpopmax&#xff0c;zpopmin&#xff0c;bzpopmin 5.zrank,zrevrank,zscore 6.zrem&#xff0c;zremrangebyrank&a…

《阿里云Data+AI:开启数据智能新时代》电子书上线啦!

本书整理了阿里云在DataAI领域的最新实践案例与深度洞察&#xff0c;涵盖电商、游戏、营销、数字内容等多个行业的成功经验&#xff0c;以及技术专家对数据库与AI融合趋势的专业解读。 通过理论与实践的结合&#xff0c;我们将共同探索DataAI如何成为企业智能化转型的核心驱动…

Golang编译器DIY,手搓 if err != nil { return err } 语法糖

前序 在go的社区里&#xff0c;下面这三行代码是被吐槽的最多的 if err ! nil {return err }从代码之整洁美观的角度看&#xff0c;这样的写法也是让人不舒服的。尤其是 当有很多错误需要处理的时候&#xff0c;就会发现通篇都是这三行。 所以想着看看修改一下编译器&#xf…

图解多头注意力机制:维度变化一镜到底

目录 一、多头注意力机制概述二、代码实现1. pyTorch 实现2. tensorFlow实现 三、维度变化全流程详解1. 参数设定2. 维度变化流程图3. 关键步骤维度变化 四、关键实现细节解析1. 多头拆分与合并2. 注意力分数计算3. 掩码处理技巧 五、完整运行示例六、总结与常见问题1. 核心优势…

[ISP] 人眼中的颜色

相机是如何记录颜色的&#xff0c;又是如何被显示器还原的&#xff1f; 相机通过记录RGB数值然后显示器显示RGB数值来实现颜色的记录和呈现。道理是这么个道理&#xff0c;但实际上各厂家生产的相机对光的响应各不相同&#xff0c;并且不同厂家显示器对三原色的显示也天差地别&…

解锁MySQL 8.0.41源码调试:Mac 11.6+CLion 2024.3.4实战指南

文章目录 解锁MySQL 8.0.41源码调试&#xff1a;Mac 11.6CLion 2024.3.4实战指南前期准备环境搭建详细步骤安装 CLion安装 CMake 3.30.5准备 MySQL 8.0.41 源码配置 CMake 选项构建 MySQL 项目 调试环境配置与验证配置 LLDB 调试器启动调试验证调试环境 总结与拓展 解锁MySQL 8…

关于xcode Project navigator/项目导航栏的一些说明

本文基于 xcode12.4 版本做说明 首先要明确一点&#xff0c;导航栏这里展示的并不是当前工程在电脑硬盘中的文件结构&#xff0c;它展示的是xxxxxx.xcodeproj/project.pbxproj文件(后文简.pbxproj文件)中的内容。我们在导航栏中的操作就是修改该文件&#xff0c;有些操作会修…

深度解析扣减系统设计:从架构到实践

背景 在当今数字化业务蓬勃发展的时代&#xff0c;扣减系统在众多业务场景中扮演着关键角色。无论是电商平台的库存扣减&#xff0c;还是金融领域的资金扣减、积分系统的积分扣减&#xff0c;一个高效、可靠且数据一致的扣减系统都是业务稳健运行的基石。本文将深入探讨扣减系…

视觉定位项目中可以任意修改拍照点位吗?

修改拍照点位不是那么简单 1. 背景2. 修改拍照点位意味着什么&#xff1f;3. 如何解决这个问题&#xff1f; 1. 背景 在视觉定位的项目中&#xff0c;会遇到这么一种情况&#xff1a;完成三步&#xff08;9点标定&#xff0c;旋转中心标定&#xff0c;示教基准&#xff09;之…

深度学习常用操作笔记

深度学习常用操作笔记 指令报错cannot import name Config from mmcvImportError: cannot import name print_log from mmcvImportError: cannot import name init_dist from mmengine.runnerWARNING: Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNon…