一、使用场景 :
1.功能实现需多次进行数据库操作
2.方法中包含请求第三方API接口
二、第三方API使用遇到的场景
(1) 第三方API成功或失败不影响主体业务的进行,如消息推送
处理方法: 把消息推送的方法摘出来,进行异步处理,避免请求出错影响主体业务流程;
注意 : 需把异步方法抽取到另一个类中,否则@Async不生效
(2) 第三方API与主体业务不可分割(如员工调动,员工离职相关API)
处理展示 :
① 请求API,根据API返回结果进行业务处理(进行数据库操作)
② 先进行数据库操作,再请求第三方的API
思考:①和②两种处理方式的区别?
① 请求API,根据API返回结果进行业务处理(进行数据库操作)
问题 : 如果API接口返回错误,则无需往下执行后续业务逻辑处理,直接终止
但是,如果API请求通过, 后续业务逻辑执行一旦出错,进行回滚, API无法回滚 , 导致事务不一致
例如员工离职,已从钉钉架构中移除员工,但是数据库状态回滚为正常状态,造成数据不一致
② 先进行数据库操作,再请求第三方的API
优点:请求第三方API发生错误时,可以回滚数据库数据;
可以得出结论第①种顺序(先API,后修改库)是有致命缺陷的,这种处理方式在代码里禁止使用!
二丶遇到API如何应该如何做?
1、API请求共性
(1)、明确成功
明确成功的意思很明显,这也是我们最常用到的成功情况,当第三方给你返回200状态码或OK时,就可以是明确成功了。
可能存在的问题:请求时间过长
(2)、明确失败
明确成功的意思很明显,这也是我们最常用到的失败情况,当第三方给你返回非200状态码或OK时,就可以是明确失败了,你就可以处理回滚了。
可能存在的问题:请求时间过长
(3)、结果未知
当你请求发过去了,因为第三方处理时间过长(可能是他们服务器有问题,也可能是业务复杂),导致连接直接断开了,谁也不知道他那边是否处理完成了。
可能存在的问题:超时情况
2、API请求存在的问题
(1)、API请求超时无法处理
请求API超时,程序判定修改失败,事务回滚, 但是API方法实际还在执行,导致数据不一致。
(2)、API请求时间过长,长时间占用库问题
虽然API请求成功了,但是20s才处理完成,因为在上面我们对自己库的操作已经在事务里面了,还未提交,所以将导致我们的操作被锁定20s,当堆积的多了,就会产生问题。
3、解决方案:
方案1: 解决超时问题
如果有条件- 调用方和被调用方协调好,可以为调用方提供回调或轮询查询接口
回调:比如超级店长调用千云,千云给超级店长提供一个回调接口,返回处理结果
轮询:超级店长定时去请求千云,获取结果
这样做的好处?
通过被调用方提供的回调或轮询功能,我们能够明确的知道自己调用是否完全成功或完全失败,不会存在模棱两个的情况。保证了第三方API结果的确定性,为事务的一致性构建了前提。
这样做的缺点
这样做没有缺点,正规的程序都会提供,唯一的缺点就是不正规的可能不提供,这也就要求我们在为别人提供业务接口的时候,要根据业务的重要程度,为他人提供回调或轮询方法。
方案2:解决库占用问题
启用编程式事务,包裹住对数据库修改的操作
// 方法上不加事务
public SimpleMessage testA() {
// 进行校验参数,一些查询
Select ***
// 开启事务,进行一些数据库修改操作
transactionTemplate.execute(status -> {
update ***
update ***
return null;
});
// 进行第三方API请求
httpUtils.post***;
return new SimpleMessage(ErrorCodeEnum.OK);
}
这样做的好处?
自己库的多个操作数据已经执行完成,事务也已经提交释放,且api请求不在事务中,自己的库不会被锁。
这样做的缺点
虽然库不被占用了,但是第三方请求如果失败了,事务仍然会不一致。而且还是无法解决超时问题,有可能存在超时情况,结果未知导致事务不一致。
方案3:在①和②的基础上改进
在方案②的基础上对失败的情况恢复处理(使用编程式事务)
在方案①的基础上使用 回调/轮询处理
// 方法上不加事务
public SimpleMessage testA() {
// 进行校验参数,一些查询
Select ***
// 开启事务,进行一些数据库修改操作
transactionTemplate.execute(status -> {
update num = 2 where ***
update ***
return null;
});
// 进行第三方API请求
httpUtils.post***
// 根据API请求返回结果, 如果失败则对数据进行回退操作
// 开启事务,进行数据恢复操作
transactionTemplate.execute(status -> {
update num = 1 where ***
update ***
return null;
});
return new SimpleMessage(ErrorCodeEnum.OK);
}
提示
TransactionTemplate执行时,不论发生受检查的异常(Exception),还是不受检查的异常(RuntimeException),都会执行回滚操作,所以无需担心事务不提交问题导致的重大bug。
这样做的好处
1、防止了库被占用,且得到失败的结果后能进行手动回滚。
这样做的缺点
1.写起来比较麻烦
2.如果第三方不提供回调或轮询,还是没办法保证事务一致性,所以第三方很重要。如果第三方提供,搭配①方案使用,就是完美解决方案,如果第三方不提供,将不存在完美解决方案,也间接说明这个业务不是很重要且开发人员很拉跨。