java两个事务方法调用 两个service调用事务_幂等性


一、话不多说,先上结论


java两个事务方法调用 两个service调用事务_幂等性_02


二、业务场景概述


java两个事务方法调用 两个service调用事务_数据库连接_03



问题概述

如何保证A订单系统和B物流系统中订单的当前状态保持一致?

实战演练代码

client-service

为了更好的测试在分布式环境下可能存在的问题,测试前我们先把连接池的最大连接数,改小一点,如下是修改Durid的这两个配置:


java两个事务方法调用 两个service调用事务_版本号_04


controller


java两个事务方法调用 两个service调用事务_数据库连接_05


service


java两个事务方法调用 两个service调用事务_数据库连接_06


target-service

controller


java两个事务方法调用 两个service调用事务_版本号_07


2. 使用Spring自带的事务注解

在一定程度上可以保证数据的一致性,但是无法成熟较大的访问量

存在数据库连接数超出,系统崩溃的风险

实战演练

测试前准备

1、配置连接池最大连接数为2

2、为发货方法添加@Transactional注解

测试操作

1、调用订单发货接口,发货订单2

2、同时调用订单发货接口,发货订单3

3、前两者发货请求执行过程中,同时调用订单查询接口

测试结果

订单1查询接口报错

错误提示

数据库连接池中的连接不足,超过最大连接数

原因分析

1、发货操作需要调用其他系统进行处理,中间存在一定的时间间隔

2、为发货操作添加事务保证数据一致性,但是整个操作在其他系统没有返回结果之前,每个发货操作都会占用一个数据库连接

3、综上分析,多个发货操作下,连接数超出最大允许数量是肯定的

三、使用编程事务

实现方案简述

1、在系统A中引入中间状态

2、发货请求收到以后,将该订单状态更新为中间状态,这一步操作添加事务

3、无事务状态请求B系统进行发货操作

4、B系统请求成功后,将订单状态更新为结果状态,这一步操作单独添加另一个事务

5、特殊情况处理:假如B系统出现故障,部分订单更新至中间状态,可使用调度系统执行一个job,统一对这部分订单进行B系统发货操作状态同步

实战演练

在基础代码上更新的内容


java两个事务方法调用 两个service调用事务_幂等性_08


OrderServiceImpl更新内容


java两个事务方法调用 两个service调用事务_两个service事务统一_09


测试操作

同Spring事务测试步骤

运行结果

订单1可一正常查询

订单2、订单3发货操作与订单1查询操作无影响

小结

使用编程事务减少了数据库连接的占用与消耗

大大提高了系统心梗,减少了数据库连接资源的浪费

不足之处

当用户高频或者误触,多次请求发货操作或这下订单操作的时候

存在想B系统发送多个重复下订单的请求

实战演练


java两个事务方法调用 两个service调用事务_版本号_10


测试结果


java两个事务方法调用 两个service调用事务_幂等性_11


分析:由于用户请求次数过多,导致在短时间内向物流发货接口发出了许许多重复的请求,这样就会导致在物流系统中接受到相同的订单发货请求,如果不进行幂等性处理,可能会造成数据错乱的风险。

四、编程事务的进一步优化

CAS锁(基于状态机的乐观锁)

对于编程事务中出现的高频请求下的异常情况,也就是幂等性问题,此处介绍一种比较简单的解决方案来实现服务调用的幂等性

CAS锁

类似于ReentrantLock的实现原理

此处就是为数据添加一个版本好,也可以称呼为状态

更新操作前查询数据,获取到旧数据的版本号

更新数据的时候以查询数据时版本号和此时数据库中版本号的关系作为能否更新数据的条件

实战演练


java两个事务方法调用 两个service调用事务_两个service事务统一_12


updateSql


java两个事务方法调用 两个service调用事务_版本号_13


总结

基于状态机的乐观锁,在一定程度上可以解决重复订单的问题