对资金类服务幂等设计与测试的思考

之前写过一篇《系统设计的幂等性》科普文章。

幂等性原本是数学上的概念,用在接口上就可以理解为:同一个接口,多次发出同一个请求,必须保证操作只执行一次。调用接口发生异常并且重复尝试时,总是会造成系统所无法承受的损失,所以必须阻止这种现象的发生。

对于本文讨论的资金类服务类,如果幂等设计不合理,则会出现一笔交易重复打款的情况,这就出现了资损。

01/ 为什么会产生接口幂等性问题?

其实可以分两类:

一类是受不可抗且非常规操作导致的重复请求,例如网络波动引起的重复请求;用户重复操作,用户在操作时候可能会无意触发多次下单交易,甚至没有响应而有意触发多次交易应用。

另一类则是请求失败需要重复发起请求的场景,例如用户请求受理成功,需要异步通过定时任务重复执行的情况。

02/ 如何保证接口幂等性?

幂等的核心是确保唯一性。常规的方法有:

唯一索引:在数据库中建立唯一索引,用作幂等键,可以防止插入重复的数据。

状态机约束: 在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机(状态变更图),就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。

Token机制:其实本质和唯一索引类似。

摆了这么多,下面引出本文讨论的重点:资金类服务如何做幂等设计与测试

当然回答问题前,还是要聊一下创作来源,这个话题的来源同样来源于对线上问题的思考。下面我娓娓道来。

03/ 问题背景

**退款服务的场景如下:**第一次请求:如果用户首次请求应用B失败,则应用A落失败的单据A-1,状态=FAIL;应用B则落受理成功的单据B-1,状态=INIT。初次请求报文如下:

{
"requestId": String,
"amount":Moeny,
"refundDetialList":[
refundDetial-1,
refundDetial-2
]
}

第二次幂等请求:后台重试发起幂等请求应用B失败,则应用A落失败的单据A-2,状态=FAIL;应用B则不对单据B-1做更新,状态=INIT。此时请求报文如下:

{
"requestId": String,
"amount":Moeny,
"refundDetialList":[
refundDetial-1,
refundDetial-2
]
}

第三次幂等请求:后台重试发起发起幂等请求应用B成功,则应用A落成功的单据A-3,状态=SUCCESS;应用B更新单据B-1状态=SUCCESS。(但是应用A与应用B之间的核对不一致。)

  • 但是此时应用A调用应用B的请求报文里refundDetialList的size数量已经包含了A-1、A-2的单据(其实这时候报文是错误的)。

此时请求报文如下:

{
"requestId": String,
"amount":Moeny,
"refundDetialList":[
refundDetial-1,
refundDetial-2,
refundDetial-3,
refundDetial-4
]
}

应用A和B针对幂等请求的处理逻辑:

应用A:幂等字段为requestId,同时校验amount的一致性。捞起DB里根据requestId+status=FAIL关联到的refundDetial组装 对应用B调用的报文。

应用B:幂等字段为requestId,同时校验amount的一致性。捞起DB里根据requestId关联到refundDetial组装报文对下游发起调用。

背景应该描述清楚了,那么下面分析一下问题:

  1. **应用A为什么组装报文错误?**原因:如果幂等重试在2次以上,组装请求报文由于DB里已经有两条FAIL出现重复组装refundDetialList的情况,导致传给下游错误的报文信息。

  2. 应用B面对错误的报文,为什么能处理成 **功?**原因:应用B会将首次请求失败的数据落DB,幂等请求时候,如果requestId不变,且amount不变,则会捞起首次请求落DB的refundDetial,组装对下游的请求报文,因此不会报错。

分析问题与原因后,乍一看感觉应用A和B都有问题,应用A组装refundDetialList的逻辑错误,忽略了2次幂等以上的场景,如果出现这种场景,则组装refundDetialList时候最起码要getFirst()才行。

应用B没有感知到应用A的错误报文,最起码应该拦截到,而不是向下调用才行。

当然,本文的目的不是争个谁对谁错,而是讨论针对这种资金类服务应该如何设计幂等?

04/ 幂等检验的范围有哪些?

资金类服务的特点就是很容易发生资损。因此在设计幂等逻辑的时候,需要分析请求报文中哪些字段需要严格"幂等"(就是重复请求中哪些字段需要严格保持一致)。

如上文说到的服务,是以requestId作为幂等字段,且对amount做了金额一致性校验,通过后才能继续向下调用。那么refundDetialList是否应该纳入幂等校验的范畴之内的,我觉得这个需要By业务分析,单纯从平台角度无法给出特定的结论。因为平台要提供的是通用能力,理论上是不吃业务的,但是如果针对业务诉求强,那就需要做业务定制的处理逻辑,这显然违背平台设计的初衷。但有时候确实是这样,平台建设没法完全脱离业务。

05/ 幂等测试场景分析

对于测试来说,通常情况下,我们分析幂等场景一般幂等字段(也可能是多个字段联合作为幂等条件)、关键资金字段如金额作为场景因子设计幂等测试用例:

CASE001:同号发起(幂等条件不变)+不换金额,预期幂等REPEAT_REQUEST;

CASE002:同号发起(幂等条件不变)+换金额,预期幂等校验不通过报错;

CASE003:换号发起(幂等条件变化)+不换金额,预期作为新请求处理;

此外,幂等发起的次数也应该引起关注,作为一个场景因子来对待:

幂等请求次数建议考虑2次以上的场景。

当然,我们设计幂等场景的时候,最好要review下开发的实现思路,不要完全采取黑盒方法,结合白盒方法设计的测试用例才更有效。

- END -


下方扫码关注 软件质量保障,与质量君一起学习成长、共同进步,做一个职场最贵Tester!

新年送福

关注「软件质量保障」微信公众号

菜单栏对话框回复:2024

点击「抽奖」小程序,即可参与新年抽奖活动

我们将从中抽取25位幸运读者,分别为大家送上新年礼物。

温馨提醒:

  1. 以上两个活动的截止日期均为:2024年1月22日23:59。

  2. 中奖的同学,请填写好邮寄地址,我们会核实后邮寄礼品。如果两周内没有收到礼品,请在后台留言或者私信我们。

好文推荐

往期推荐

聊聊工作中的自我管理和向上管理

经验分享|测试工程师转型测试开发历程

聊聊UI自动化的PageObject设计模式

细读《阿里测试之道》

相关推荐
VVVVWeiYee2 分钟前
项目2路由交换
运维·服务器·网络·网络协议·信息与通信
lifeng43211 小时前
Jenkins集成部署(图文教程、超级详细)
运维·jenkins
2401_882727571 小时前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
NoneCoder1 小时前
CSS系列(36)-- Containment详解
前端·css
anyup_前端梦工厂1 小时前
初始 ShellJS:一个 Node.js 命令行工具集合
前端·javascript·node.js
5hand2 小时前
Element-ui的使用教程 基于HBuilder X
前端·javascript·vue.js·elementui
白手小弟2 小时前
python wxauto库实现微信自动化发送信息、回复、添加好友等
运维·自动化
ii_best2 小时前
ios按键精灵自动化的脚本教程:自动点赞功能的实现
运维·ios·自动化
3DVisionary2 小时前
数字图像相关DIC技术用于机械臂自动化焊接全场变形测量
运维·数码相机·自动化·焊接变形实验·数字图像相关dic技术·自动化焊接全场变形测量·非接触高精度环境适应性全场测量
小伍_Five2 小时前
透视网络世界:计算机网络习题的深度解析与总结【前3章】
服务器·网络·计算机网络