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

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

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

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

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设计模式

细读《阿里测试之道》

相关推荐
你好,赵志伟1 小时前
Socket 编程 TCP
linux·服务器·tcp/ip
Liang_GaRy2 小时前
心路历程-三个了解敲开linux的大门
linux·运维·服务器
星霜笔记3 小时前
Docker 部署 MariaDB+phpMyAdmin+Nextcloud 完整教程
运维·数据库·docker·容器·mariadb
阿珊和她的猫4 小时前
v-scale-scree: 根据屏幕尺寸缩放内容
开发语言·前端·javascript
一只栖枝6 小时前
华为 HCIE 大数据认证中 Linux 命令行的运用及价值
大数据·linux·运维·华为·华为认证·hcie·it
wuicer8 小时前
ubuntu 20.04 安装anaconda以及安装spyder
linux·运维·ubuntu
加班是不可能的,除非双倍日工资8 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi9 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip9 小时前
vite和webpack打包结构控制
前端·javascript
玩转以太网9 小时前
基于W55MH32Q-EVB 实现 HTTP 服务器配置 OLED 滚动显示信息
服务器·网络协议·http