【商城实战(38)】Spring Boot:从本地事务到分布式事务,商城数据一致性的守护之旅
【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 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 {@Autowiredprivate OrderRepository orderRepository;@Transactionalpublic 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 {@Autowiredprivate TransactionTemplate transactionTemplate;@Autowiredprivate 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 {@Autowiredprivate PlatformTransactionManager transactionManager;@Autowiredprivate 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 {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate StockTccService stockTccService;@Overridepublic 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());}@Overridepublic boolean confirmCreateOrder(Order order) {// 将临时订单信息保存到正式订单表orderRepository.saveOrder(order);// 调用库存服务的Confirm方法确认扣减库存return stockTccService.confirmDeductStock(order.getProductId(), order.getQuantity());}@Overridepublic 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 {@Autowiredprivate StockRepository stockRepository;@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 {@Autowiredprivate 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 模式的代码侵入性等。另一方面,新的分布式事务技术和框架可能会应运而生,以更好地适应云原生、微服务等新兴架构的发展趋势。同时,人工智能和大数据技术也可能会与分布式事务处理相结合,通过智能算法来优化事务的调度和处理,提高系统的整体性能和可靠性。对于商城项目开发者来说,持续关注分布式事务技术的发展动态,不断学习和应用新的技术,将是保障商城系统稳定运行和数据一致性的重要途径。
相关文章:
【商城实战(38)】Spring Boot:从本地事务到分布式事务,商城数据一致性的守护之旅
【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配…...
漏洞知识点《PHP数组绕过深入解析》
在PHP中,通过数组绕过安全限制的核心原理与PHP语言特性和底层实现机制密切相关。以下是具体原因及技术细节分析: 一、PHP参数解析机制的特性 PHP的$_GET、$_POST等超全局变量支持将用户输入自动解析为数组。例如,通过URL参数?username[0]a…...
【极光 Orbit·STC8x】05. GPIO库函数驱动LED流动
【极光 OrbitSTC8】05. GPIO库函数驱动LED流动 七律 逐光流转 八灯列阵若星河,状态为舟渡长波。 寄存器中藏玄机,Switch语句定山河。 循环往复如潮涌,步骤变量掌沉浮。 单片机前展锋芒,代码织就光之舞。 摘要 本文基于STC8H8K6…...
SSH配置过程及无法正常链接问题的解决【小白教学】
1.尝试克隆github上的项目,发现无法正常下载【之前有些是可以的】 git clone https://github.com/mogualla/PythonRobotics.git --depth 3 出现下面的提示【错误】: Cloning into PythonRobotics... fatal: unable to access https://github.com/mogua…...
总结 HTTP 协议的基本格式, 相关知识以及抓包工具fiddler的使用
目录 1 HTTP是什么 2 HTTP协议格式 3 HTTP请求(Request) 3.1 认识URL 3.2 方法 3.3 认识请求"报头"(header) 3.3.1 Host 3.3.2 Content-Length 3.3.3 Content-Type 3.3.4 User-Agent (简称UA) 3.3.5 Referer 3.3.6 Cookie和Session 4 HTTP响应详解 4.…...
Conda 虚拟环境创建:加不加 Python 版本的深度剖析
在 conda 中创建虚拟环境时,是否指定 Python 具体版本会直接影响环境构建的底层逻辑、依赖管理方式以及后续开发的可控性。 一、核心机制对比 不指定 Python 版本 (conda create -n env_name) 默认继承基础环境版本 Conda 会使用当前基础环境(如 base&am…...
docker的anythingllm和open-webui压缩包分享(国内镜像拉取,百度云压缩包分享)
文章目录 前言第一部分:镜像获取🚀 方式一:切换国内下载镜像✅1. 下载anythingllm✅ 2. 下载open-webui 🚀方式二:下载我分享的百度云✅ anythingllm压缩包百度云链接❎ open-webui压缩包 第二部分:下载之后…...
C#命令行参数用法
C#命令行参数用法 static void Main(string[] args){Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);// 解析命令行参数if (args.Length > 0){// 这里处理命令行参数,例如:打开文件、设置配置等// 例如&…...
Unity3D开发AI桌面精灵/宠物系列 【二】 语音唤醒 ivw 的两种方式-Windows本地或第三方讯飞等
Unity3D 交互式AI桌面宠物开发系列【二】ivw 语音唤醒 该系列主要介绍怎么制作AI桌面宠物的流程,我会从项目开始创建初期到最终可以和AI宠物进行交互为止,项目已经开发完成,我会仔细梳理一下流程,分步讲解。 这篇文章主要讲有关于…...
Matlab概率区间预测全家桶更新了,新增光伏出力区间预测,4种分布可供预测
基本介绍 适用于matlab2020及以上。可任意选择置信区间,区间覆盖率picp、区间平均宽度百分比等等,可用于预测不确定性,效果如图所示,采用KDE,4种分布进行预测,有对比,可以替换成自己的数据。 …...
第2章:容器核心原理:深入理解Namespace、Cgroup与联合文件系统
第2章:容器核心原理:深入理解Namespace、Cgroup与联合文件系统 作者:DogDog_Shuai 阅读时间:约20分钟 难度:中级 目录 1. 引言2. Linux容器核心技术3. Namespace详解4. Cgroup详解5. 联合文件系统6. 容器运行时原理...
用css绘制收银键盘
最近需求说需要自己弄个收银键盘,于是乎直接上手搓 主要基于Vue3写的,主要是CSS <template><view class"container"><view class"info"><image class"img" src"" mode"">&l…...
aws训练快速入门教程
AWS 相关核心概念 简洁地介绍一下AWS训练云服务的核心关联概念: AWS核心服务层: 基础设施层: EC2(计算), S3(存储), RDS(数据库)等人工智能层: SageMaker(训练平台), AI服务等 机器学习服务分级: 高层: 预构建AI服务(开箱即用)中层: SageMaker(主要训练平台)底层: 框架和基…...
基于FPGA轨道交通6U机箱CPCI脉冲板板卡
板卡简介: 本板为脉冲板,脉冲板主要执行CPU下达的指令,通过实现各种控制算法来调节PWM,然后输出光纤PWM信号来驱动变频器功率模块以达到控制电机的目的。 性能规格: 电源:DC5V;15V FPGA&…...
数据库GreenDao的使用、升级、以及相关常用注释说明
目录 一、使用GreenDao的流程 添加GreenDao依赖配置greendao的generator生成文件使用GreenDao生成bean类 3.1 创建实体类 3.2 生成dao文件创建GreenDaoManager来进行统一管理,并初始化 4.1 创建GreenDaoManager 4.2 在Application中进行初始化GreenDao使用GreenDa…...
【C++】 —— 笔试刷题day_6
刷题day_6,继续加油哇! 今天这三道题全是高精度算法 一、大数加法 题目链接:大数加法 题目解析与解题思路 OK,这道题题目描述很简单,就是给我们两个字符串形式的数字,让我们计算这两个数字的和 看题目我…...
PostgreSQL:语言基础与数据库操作
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
cmake 之 CMakeLists.txt 中的函数是从哪里来的
我们都知道,cmake会解释执行 CMakeLists.txt 以及其他 *.cmake 脚本, 这里先给出一个“先验” 的知识点: 任何一个独立脚本或脚本函数命令的执行,都是通过 CPP 函数 RunListFile(...) 调用的 void cmMakefile::RunListFile(cmL…...
谷歌or-tools开源库入门
1.命令行编译程序 这里要说明下,直接用qt或者VS2022打开cmake工程,编译没有成功。所以,老老实实的按照官方教程来,使用命令行编译。 (1)准备 1)安装cmake,版本3.18以上࿰…...
深入解析 C++ Vector:全面掌握 STL 核心容器的原理与高效实践
一、Vector 的核心概念与特性 Vector 是 C 标准库中最常用的动态数组容器,其底层基于连续内存存储元素,兼具数组的高效访问与动态扩容的灵活性。以下是其核心特性: 1.1 核心特性对比 特性普通数组Vector 容器内存分配静态固定动态增长访问效…...
【MySQL】MySQL数据存储机制之存储引擎
目录 1.如何理解存储引擎? 2.MySQL 提供的存储引擎 3.存储引擎的功能特性 (1)存储介质 (2)事务处理能力 (3)锁定 (4)备份和恢复 (5)优化…...
OpenCV旋转估计(1)用于估计图像间仿射变换关系的类cv::detail::AffineBasedEstimator
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 基于仿射变换的估计器。 这种估计器使用匹配器估算的成对变换来为每个相机估算最终的变换。 cv::detail::AffineBasedEstimator 是 OpenCV 库中…...
小红书不绑定手机号会显示ip吗
小红书作为一个生活方式分享平台,拥有庞大的用户群体。在小红书上,用户可以分享自己的生活点滴、购物心得、美食体验等,与其他用户进行互动交流。最近,不少用户对于小红书是否会在不绑定手机号的情况下显示IP属地产生了疑问&#…...
网络空间安全(36)数据库权限提升获取webshell思路总结
一、获取数据库访问权限 寻找漏洞: SQL注入:这是最常见的方法之一。攻击者通过SQL注入漏洞,可以在数据库执行任意SQL语句,从而获取数据库中的数据,甚至可能获取数据库的访问权限。配置文件泄露:有时&#x…...
OceanBase 中,如何抓包分析应用连接超时的问题
本文作者:胡呈清,爱可生 DBA 团队成员,擅长故障分析、性能优化 与MySQL这类单机数据库相比,OceanBase分布式数据库的访问链路相对较长,因此在遇到连接异常时,排查过程需要额外考虑更多环节。接下来…...
用uv管理python环境/项目(各种应用场景)
一、安装uv 有python的情况 pip install uvWindows powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"linux或macOS curl -LsSf https://astral.sh/uv/install.sh | sh二、换镜像源 uv不会读取pip的镜像源配置,所…...
Linux——进程(5)进程地址空间
先看一个程序和现象 预期现象是,子进程和父进程相互独立,子进程的gval是100,101,102....而父进程一直都是100. 结果我们并不意外,只是我们发现,父子进程的gval的地址是一样的,这有点颠覆我们的认…...
docker(1) -- centos镜像
1. 前言 我在WSL中运行的系统是ubuntu2024,并安装了docker,想要在docker中运行一个centos的系统。 2. 下载并运行镜像 # 下载centos最新版镜像 $ docker pull centos Using default tag: latest latest: Pulling from library/centos a1d0c7532777: P…...
Vitis 2024.1 无法正常编译custom ip的bug(因为Makefile里的wildcard)
现象:如果在vivado中,添加了自己的custom IP,比如AXI4 IP,那么在Vitis(2024.1)编译导出的原本的.xsa的时候,会构建build失败。报错代码是: "Compiling blank_test_ip..."…...
【源码阅读】多个函数抽象为类(实现各种类型文件转为PDF)
目录 一、原始函数二、类三、转换过程 一、原始函数 最开始就是写了几个函数(包括doc、excel、ppt类型的文件)转换为pdf,需要将这些函数形成一个类。相似的一类函数就可以组成一个实现特定功能的类 import subprocess import pandas as pd i…...
word插入Mathtype公式居中和自动更新
word插入公式自动更新 前提:安装Mathtype 1.word中查看页的宽度 出现如下 2.设置样式 出现这个窗口 给样式随便起个名字 3.修改样式 3.1 设置两个制表位 第二个 3.2 修改公式字体 如下所示 4. 修改公式格式 4.1在word中打开 Mathtype 4.2 修改公式的格式 变成…...
SpringSecurity配置(自定义认证过滤器)
文末有本篇文章的项目源码文件可供下载学习 在这个案例中,我们已经实现了自定义登录URI的操作,登录成功之后,我们再次访问后端中的API的时候要在请求头中携带token,此时的token是jwt字符串,我们需要将该jwt字符串进行解析,查看解析后的User对象是否处于登录状态.登录状态下,将…...
python字符级差异分析并生成 Word 报告 自然语言处理断句
import difflib from docx import Document from docx.shared import RGBColor from snownlp import SnowNLPdef analyze_char_differences(text_a, text_b):"""分析两个文本的字符级差异:param text_a: 第一个文本:param text_b: 第二个文本"""…...
企业级云MES全套源码,支持app、小程序、H5、台后管理端
企业级云MES全套源码,支持app、小程序、H5、台后管理端,全套源码 开发环境 技术架构:springboot vue-element-plus-admin 开发语言:Java 开发工具:idea 前端框架:vue.js 后端框架ÿ…...
使用GoldenGate完成SQLserver到Oracle的数据实时同步
一、环境准备 *项目**源环境**目标环境*操作系统CentOS Linux release 7.6CentOS Linux release 7.6IP地址192.168.3.92192.168.3.168数据库及版本SQLserver 2016Oracle 11.2.0.4.0GoldenGate用户oggoggGoldenGate版本12.3.0.2.012.3.0.2.0 二、OGG架构 GoldenGate v11 能够…...
【OpenCV C++】如何快速 高效的计算出图像中大于值的像素个数? 遍历比较吗? No,效率太低!那么如何更高效?
文章目录 1 问题2 分析3 代码实现 (两种方法实现)方法1: 使用cv::compare方法2: 使用cv::threshold3.2 compare和threshold 看起来都有二值化效果? 那么二者效率?4 compare函数解释4.1 参数解释4.2 底层行为规则4.3 应用示例4.4 典型应用场景1 问题 一幅图像的目标区域ROI…...
Golang | 每日一练 (6)
💢欢迎来到张胤尘的技术站 💥技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥 文章目录 Golang | 每日一练 (6)题目参考答案什么是内存逃逸&am…...
git clone, 算是解决可以访问github但无法clone的问题
本文的前提是使用了**且可以正常访问github 查看代理的端口 将其配置到git 首先查看git配置 git config --list然后添加配置,我这边使用的是Hiddfy默认的端口是12334,如果是clash应该是7890 git config --global http.proxy 127.0.0.1:12334其他 删除…...
SpringBoot项目controller层接收对应格式请求的相关RequestMapping配置
目录 (1) (2) (3) 注:此情况注意和(4)中情况进行区分 (4) 在几个springboot项目开发后,我总结了以下的一些常见的接收对应请求的…...
基于ssm学科竞赛小程序的设计及实现(源码+lw+部署文档+讲解),源码可白嫖!
摘要 随着信息时代的来临,过去的学科竞赛管理方式的缺点逐渐暴露,本次对过去的学科竞赛管理方式的缺点进行分析,采取计算机方式构建学科竞赛小程序。本文通过阅读相关文献,研究国内外相关技术,提出了一种关于竞赛信息…...
【论文笔记】VGGT-从2D感知3D:pose估计+稠密重建+点跟踪
VGG组联合Meta改进了dust3r,输入图片,输出对应的一系列3D属性,被CVPR2025收录! 1.abstract 我们提出了VGGT,一种前馈神经网络,能够直接从场景的一个、几个或数百个视角推断出所有关键的3D属性,…...
【大模型系列篇】硅基智能开源数字人模型HeyGem.ai,开启数字人时刻
硅基智能开源数字人模型HeyGem.ai, 1秒克隆生成4K视频, 支持离线多语言, 开源72小时狂揽1.3k星, 目前已经获得3.4k星。 硅基智能正式宣布在GitHub开源全球TOP级数字人模型,同时发布基于该模型的同名数字人工具硅基数字人克隆的本地安装包,这一举措标志着…...
腾讯云容器集群:节点可以访问公网,节点内的pod无法访问公网
腾讯云容器集群:节点可以访问公网,节点内的pod无法访问公网 curl https://www.baidu.com/index.htm参考链接:https://cloud.tencent.com/document/product/457/50356 sysctl -a|grep net.ipv4.conf.all.rp_filter sysctl -a|grep net.ipv4.c…...
Winform优化控件布局性能 SuspendLayout 和 ResumeLayout 方法详解
在Winform中,SuspendLayout 和 ResumeLayout 方法用于优化控件布局性能,适用于批量修改控件属性或动态调整控件时的场景。以下是具体使用方法和注意事项: 一、基本用法 1.调用 SuspendLayout() 在开始批量修改控件前,调用…...
基于Netty实现高性能HTTP服务的架构解析
一、HTTP协议基础 1.1 HTTP协议概述 HTTP(HyperText Transfer Protocol)作为现代Web应用的基石,是基于TCP/IP的应用层协议,具有以下核心特性: 请求/响应模型:客户端发起请求,服务端返回响应无…...
Sqlite下载、安装与数据库创建
Sqlite官网 https://www.sqlite.org/index.html 官方文档链接 https://www.sqlite.org/docs.html 官方文档是英文版的,如果想看中文的文档请参考 **菜鸟教程** 网站中的 **《Sqlite教程》:https://www.runoob.com/sqlite/sqlite-tutorial.html 官方下载…...
内网环境安装dlv,本地远程调试go
背景:内网环境(服务器)下安装dlv,本地通过dlv调试编译后的go代码。 可以配合观看: 【dlv远程调试-哔哩哔哩】 https://b23.tv/NqPZ5q9 内网安装dlv步骤 1、dlv安装: (我额服务器和内网的go都是1.21以上) # 先在有网络的环境下(…...
【使用 Element UI 实现手动上传文件:FormData 追加文件和其他参数,支持单文件覆盖上传】
在开发 Web 应用时,文件上传是一个常见的需求。Element UI 提供了强大的 el-upload 组件,可以轻松实现文件上传功能。本文将详细介绍如何使用 Element UI 实现以下功能: 手动触发文件上传:用户选择文件后,点击按钮手动…...
python基础8 单元测试
通过前面的7个章节,作者学习了python的各项基础知识,也学习了python的编译和执行。但在实际环境上,我们需要验证我们的代码功能符合我们的设计预期,所以需要结合python的单元测试类,编写单元测试代码。 Python有一个内…...
第四节:sqlx库使用指南
在项目中我们通常可能会使用database/sql连接MySQL数据库。本文借助使用sqlx实现批量插入数据的例子,介绍了sqlx中可能被你忽视了的sqlx.In和DB.NamedExec方法。 sqlx介绍 在项目中我们通常可能会使用database/sql连接MySQL数据库。sqlx可以认为是Go语言内置datab…...