#BUG SHOW# 深挖一个6年前的老“bug”

引言

最近参与了一个业务迁移的项目,需要把站点A迁移到站点B。不同的站点拥有各自独立的服务和数据库,可以说是毫无关联。为了兼容迁移过程中的存在的一部分特殊交易数据(正向[支付]交易在站点A,但逆向[退款]操作在站点B操作),因此需要做站点A和站点B数据的关联,形如下图,在目前迁移站点落的单据拼接站点A的交易单号。

原本站点B表的字段设计长度为32,这样对站点A的单据做个拼接后就出现了字段超长的情况【针对特殊业务做的处理方式,非平台通用能力】,于是乎在落幂等表(一般是xx_unique表)时出现insert异常,但是这个异常并未直接阻断后续的业务流程,而如果以相同的内容(交易单据)再此发起接口请求,就可能会(本文中就是如果没将transId作为primary key的话,就会重复插入数据)产生了重复落(业务表)单的缺陷。

非字段超长的场景:第一笔请求进来,则会落幂等表一条数据、一笔业务表数据;第二笔重复请求,则会查询幂等表是否已经存在交易单,存在则阻断业务操作,反之继续走业务操作落幂等表&业务表数据。

大家应该了解到了,这其实也是一个幂等相关的缺陷,我先对幂等做个科普,再对缺陷进行复现&探讨解决方案。下面是百度百科对于幂等的解释:

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等方法是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号实现。

幂等的实现方式

先介绍一下几个概念:

  1. 幂等(去重)表,利用数据库表单的特性来实现幂等。以订单请求支付场景为例:将订单号orderId设为去重表的唯一索引,每次请求支付都根据订单号向去重表中插入一条数据,只有插入成功才继续执行支付操作,相当于在事务的开始阶段加锁。

  2. (联合)主键。PRIMARY KEY 约束唯一标识数据库表中的每条记录。主键必须包含唯一的值。主键列不能包含 NULL 值。每个表都应该有一个主键,并且每个表只能有一个主键。

  3. 幂等返回的错误码。一般是REPEAT_REQUEST,返回上游用于判断是否重复请求。

基于 mysql 实现

这种实现方式是利用 mysql 唯一索引的特性。示意图如下:

具体流程步骤:

  1. 建立一张幂等表,其中某个字段需要建立唯一索引

  2. 客户端去请求服务端,服务端会将这次请求的一些信息插入这张去重表中

  3. 因为表中某个字段带有唯一索引,如果插入成功,证明表中没有这次请求的信息,则执行后续的业务逻辑

  4. 如果插入失败,则代表已经执行过当前请求,直接返回

基于 redis 实现

这种实现方式是基于 SETNX 命令实现的。SETNX key value:将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作。

该命令在设置成功时返回 1,设置失败时返回 0。示意图如下:

具体流程步骤:

  1. 客户端先请求服务端,会拿到一个能代表这次请求业务的唯一字段

  2. 将该字段以 SETNX 的方式存入 redis 中,并根据业务设置相应的超时时间

  3. 如果设置成功,证明这是第一次请求,则执行后续的业务逻辑

  4. 如果设置失败,则代表已经执行过当前请求,直接返回

缺陷复盘

一句话缺陷描述:字段超长导致幂等表数据插入异常,但是这个异常【被捕获】导致未阻断业务流程。

测试用例:幂等测试,对接口A进行幂等测试,重复请求预期返回REPEAT_REQUEST,实际UN_EXCEPTION。

代码设计

原表结构,trans_id长度是32。联调阶段,发现字段超长会导致业务表insert异常,所以对aqc_trans表字段trans_id进行了扩容到64,但是此时没有对aqc_unique.trans_id字段进行扩容。

表名非真实业务表名,只做测试用

代码实现

/** */// 接口请求实现逻辑public class TransServiceImpl {    private TransResult trans(String transId){        TransResult result = new TransResult();        // 幂等检查        UniqueCheckResult uniqueCheckResult = UniqueCheckServiceImpl.uniqueCheckAndInsert(transId);        if (uniqueCheckResult.isExist){            result.setSuccess(true);            result.setErrorDescription("REPEAT_REQUEST");            return result;        }        // 执行业务流程        TransDo transDo = new TransDo();        transDo.setTransId(transId);        Date now = new Date();        transDo.setCreatedTime(now);        insert(transDo);        result.setSuccess(true);        return result;    }}// 幂等判断逻辑public class UniqueCheckServiceImpl{    static UniqueCheckResult uniqueCheck(String transId){        UniqueCheckResult result = new UniqueCheckResult();        UniqueDo uniqueDo = uniqueQuery(transId);        if (uniqueDo!=null){            result.setExist(true);            return result;        }        return result;    }    static UniqueCheckResult uniqueCheckAndInsert(String transId){        UniqueCheckResult result = uniqueCheck(transId);        // 存在,直接return        if (result.isExist){            return result;        }        // 不存在, 需要创建unique数据        try{            UniqueDo uniqueDo = new UniqueDo();            uniqueDo.setTransId(transId);            insert(uniqueDo);            return result;        } catch (DataIntegrityViolationException e){            LogUtils.error("insert db exception");            return uniqueCheck(transId);        }    }}@Datapublic class UniqueCheckResult {    boolean isExist = false;    void setExist(boolean isExist){        isExist = this.isExist;    }}

缺陷分析

问题出在DataIntegrityViolationException,DataIntegrityViolationException是Spring框架中的一个异常类,它表示在尝试将数据插入、更新或删除到数据库时,由于数据完整性约束(例如唯一性约束、外键约束等)而导致的异常。通常情况下,这种异常是由于应用程序中的错误或者非法操作导致的,例如试图插入重复(异常)的数据或者试图删除具有外键约束的数据。

缺陷修复

缺陷的原因是,insert超长字段导致DataIntegrityViolationException被捕获,返回uniqueCheck()结果为false,进而继续执行业务流程。

可以看到上面的流程图红色箭头部分,目前缺陷就是走到了这条链路产生的【根因就是异常被捕获】;预期链路是绿色箭头表示的链路,即应该异常跳出,不执行业务逻辑。

因此目前的解法是更换被捕获的异常类型,替换成DuplicateKeyException,这样出现超长字段插入时就不会被捕获了,会直接抛出异常。

对缺陷的思考

缺陷介绍完了,但是对这个缺陷的争论(即是否将此问题归类为系统缺陷)仍没有定论。一方认为站点B当初设计表结构时候,字段长度的取值是经过业务发展评估的,请求过来的字段长度就应该不能超出定义的长度,即业务要run在平台设计能力范畴内,这样理论上永远不会存在字段超长的情况,因此这个问题不应该是缺陷,是预期外的业务接入导致的系统无法正常处理。

另一方(包括我)认为这就是一个缺陷,1. 系统自开发完成后并不是一成不变的,它需要随着业务不断演进【演进性】,即平台是服务于业务的。2. 对于测试来说,边界值测试是非常重要的,特别是异常边界值场景。3. 这个缺陷也暴露出系统设计的一大缺陷,即接口的参数校验是不严谨的。

本系统对于接口的请求内容校验只有必传值层面,而字段类型和长度并未做校验,这就导致问题延伸到了DAO层,而这是非常不合理的(理论上check request阶段就应该拦截超长字段)。

针对这个缺陷,可以举一反三。1. 此问题可能同样存在于站点B其他系统。2. 对系统的所有接口请求参数做(异常)边界值测试,把其他潜在问题暴露出来。

- END -


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

往期推荐

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

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

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

细读《阿里测试之道》

我在阿里做测开

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/188265.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用new Vue()的时候发生了什么?

前言 Vue.js是一个流行的JavaScript前端框架,用于构建单页面应用(SPA)和用户界面。当我们使用new Vue()来创建一个Vue实例时,Vue会执行一系列的初始化过程,将数据变成响应式,编译模板,挂载实例…

基于WSL2+Docker+VScode搭建机器学习(深度学习)开发环境

基于WSL2DockerVScode搭建机器学习(深度学习)开发环境 内容概述:由于最近配发了新的工作电脑但不想装双系统,因此通过本博文来记录基于Windows子系统WSLDocker搭建机器学习与深度学习开发环境的流程步骤,同时记录该过程中所遇到的相关问题及解…

FFmpeg命令分隔视频

有一个视频如a.mp4,此视频采用帧率为30生成,共有299帧,这里通过FFmpeg命令分隔成1秒一个个的小视频,即每个小视频帧数为30帧。 用到的FFmpeg参数如下所示: (1).-i:指定输入视频文件的名称; (2).-c:指…

Guitar Pro软件8.0官方最新版本下载

Guitar Pro 8是一款由法国Arobas Music公司开发的吉他学习与MIDI音序制作辅助软件,它具有丰富的功能,包括吉他谱、六线谱、四线谱绘制、打印、查看、试听等方面,能够帮助音乐爱好者更方便地进行音乐学习和创作。Guitar Pro 8拥有独特的gtp格式…

Jmeter对图片验证码的处理

​对图片验证码的处理 在web端的登录接口经常会有图片验证码的输入,而且每次登录时图片验证码都是随机的;当通过jmeter做接口登录的时候要对图片验证码进行识别出图片中的字段,然后再登录接口中使用; 通过jmeter对图片验证码的识…

【手写实现一个简单版的Dubbo,深刻理解RPC框架的底层实现原理】

手写实现一个简单版的Dubbo,深刻理解RPC框架的底层实现原理 RPC框架简介了解Dubbo的实现原理服务暴露服务引入服务调用 手写实现一个简单版的Dubbo服务暴露ServiceBeanProxyFactory#getInvokerProtocol#exportRegistryProtocol#export 服务引入RegistryProto#referD…

开源称重系统-有源代码

最近发现网上有个不错的开源称重软件,界面做的非常漂亮,收藏一下;还有硬件对接:耀华A9仪表、海康威视监控,IC卡读卡器,控制器对接都有,是个不错的软件,非常感谢作者分享;…

Jenkins 插件下载速度慢安装失败?这篇文章可能解决你头等难题!

Jenkins部署完毕,如果不安装插件的话,那它就是一个光杆司令,啥事也做不了! 所以首先要登陆管理员账号然后点击系统管理再点击右边的插件管理安装CI/CD必要插件。 但是问题来了,jenkins下载插件速度非常慢&#xff0c…

使用bard分析视频内容

11月21日的bard update 更新了分析视频的功能,使用方法如下: 1、打开bard网站。https://bard.google.com/ 2、点击插件。 3、点击YouTube中的 research a topic 选项。 4、输入需要分析的内容: Please analyze how many technologies are in…

Compose入门

​ 本篇文章主要是为了对Compose有一个初步了解。知道Compose是做什么的,用Compose能干什么,在目前的各种UI框架下面有些优势,参考Google官网的解释加上一些自己的理解生成的一篇文章。本人也是Compose初学者,通过每一步学习遇到哪…

4.前端--HTML标签3【2023.11.25】

1.表格 1.1表格的作用 表格的作用&#xff1a;表格主要用于显示、展示数据 1.2表格的基本格式 <table><tr><td>单元格内的文字</td><td>单元格内的文字</td>...</tr>... </table><table> </table> 是用于定义表…

[蓝桥杯训练]———高精度乘法、除法

高精度乘法、除法 一、高精度乘法⭐1.1 初步理解1.1.1 高精度的定义1.1.2 为什么会有高精度1.1.3 高精度乘法的复杂度 1.2 思想讲解1.3 代码实现1.3.1 声明1.3.2 实现高精度乘法1.3.3 整体实现1.3.4 代码测试 二、高精度除法⭐2.1 初步理解2.2 思想讲解2.3 代码实现2.3.1 声明2…

【Vulnhub靶机】Jarbas--Jenkins

文章目录 信息收集主机发现端口扫描目录爆破 漏洞探测whatwebhash-identifierwhatweb 文档说明&#xff1a;https://www.vulnhub.com/entry/jarbas-1,232/ 靶机下载&#xff1a;Download (Mirror): 信息收集 主机发现 扫描C段 sudo nmap -sn 10.9.75.0/24端口扫描 sudo nma…

【教学类-06-11】20231125(55格版)X-Y之间“除法÷题”(以1-9乘法口诀表倒推)(随机抽取和正序抽取)

图片展示 &#xff08;随机打乱排序&#xff09; 正序&#xff08;每张都一样&#xff09; 背景需求&#xff1a; 前面三篇写到了随机加法、随机减法、随机乘法&#xff0c;既然做了三套&#xff0c;怎么能不试试最后一款“除法”呢 模仿乘法版本&#xff0c;制作打乱版和正…

安卓用SQLite数据库存储数据

什么是SQLite&#xff1f; SQLite是安卓中的轻量级内置数据库&#xff0c;不需要设置用户名和密码就可以使用。资源占用较少&#xff0c;运算速度也比较快。 SQLite支持&#xff1a;null&#xff08;空&#xff09;、integer&#xff08;整形&#xff09;、real&#xff08;小…

前端入门(三)Vue生命周期、组件技术、脚手架、存储、事件总线、

文章目录 Vue生命周期Vue 组件化编程 - .vue文件非单文件组件组件的注意点组件嵌套Vue实例对象和VueComponent实例对象Js对象原型与原型链Vue与VueComponent的重要内置关系 应用单文件组件构建 Vue脚手架 - vue.cli项目文件结构组件相关高级属性引用名 - ref数据接入 - props混…

安卓系统修图软件(一)

平时我们会不时在朋友圈发自己的自拍照&#xff0c;或者是风景图等&#xff0c;许多小伙伴们此时会对照片进行一定的修理&#xff0c;比如添加滤镜等操作。在电脑上用ps修图比较繁琐&#xff0c;日常中大可不必用这把宰牛刀&#xff1b;而手机自带的编辑器&#xff0c;或者是QQ…

【Java基础系列】文件上传功能

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

反思一次效能提升

前天与一个大佬交流。想起自己在6年多前在团队里做的一次小小的效能提升。 改进前 在同一个产品团队&#xff0c;同时有前端工程师和后端工程师。他们经常需要共同协作完成features。 前端是一个传统的多页应用。前端渲染是由后端的velocity模板引擎实现的。 打包后&#xff0c…

【电路笔记】-分流器

分流器 文章目录 分流器1、概述2、通用/网络配置3、无功分流器3.1 电阻电容分流器3.2 电阻-电感分流器 4、总结 我们在之前关于分压器的文中已经看到&#xff0c;分压过程是通过在串联配置中关联相同的组件来实现的。 在本文中&#xff0c;我们将重点关注电流分频器执行的电流分…