软件测试商城项目优惠券超发问题该怎么测试?

【2023性能测试完整版】这可能是B站讲得最好的软件测试课程(Jmeter 接口测试实战 Loadrunner Tomcat综合教程)软件测试面试、自动化测试。

在拼夕夕面试中,面试官问了一连串经典的问题:“优惠券库存是怎么扣减的?开发为了解决超发优惠券问题而设计的方案,你了解过吗?你又是如何测试的呢?”

当时听到这些问题还挺懵的,没遇到过超发问题啊?开发设计的方案我怎么知道?现在想起来还挺幼稚的,其实现在想想电商中有很多类似的问题,比如商品超卖,归根究底,就是一个问题,那就是并发安全问题。

问题引入

就拿领取优惠券的问题来说,

需求描述:A 优惠券一共发行 100张,每一个用户最多可以领取5张。

当一个用户领取优惠券成功的时候,把领取的记录写入另外一个表中(这张表我们暂且称为表 B)。

在领取优惠券的过程中,优惠券库存的扣减过程,一般操作如下:

1、select查询优惠券的库存。

2、计算优惠券库存是否足够,如果优惠券存库不足则抛出库存不足的异常,如果优惠券库存足够,则判断是否在领取时间、判断用户领取数量是否超过个人最高领取限制。

3、如果2成立,则减去扣除的库存得到最新的库存剩余值。

4、set设置最新的优惠券库存剩余值

伪代码如下:

 扣减优惠券sql如下:

update coupon set stock = stock - 1 where id = #{coupon_id}

并发量比较低的时候,几乎看不出来有问题,可是当我们开启多线程,去请求这个抢优惠券的接口时,问题出现了,id为19的这个优惠券库存为负数。多发了一个,什么原因呢?

深入解读并发安全问题

为啥并发量高的时候会出现优惠券库存多发的问题呢?原因如下截图:

上图中出现问题的环节其实是判断优惠券库存那个步骤,重点来了:

高并发情况下,如果同时来了两个线程线程 A和线程 B(可以理解成是两个请求),比如先来的那个线程A请求通过了检查,这时线程 A 还没有扣减库存,这时经过一番操作,线程B也通过了这个检查优惠券是否可领取的方法,然后线程 A 和线程 B 依次扣减库存或者是同时扣减库存。所以就出现了刚刚数据库出现的现象,优惠券库存为-1个,就像下图。

怎么解决并发安全问题?

Java 代码加锁

synchronized (this){LoginUser loginUser = LoginInterceptor.threadLocal.get();CouponDO couponDO = couponMapper.selectOne(new QueryWrapper().eq("id", couponId).eq("category", categoryEnum.name()));if(couponDO == null){throw new BizException(BizCodeEnum.COUPON_NO_EXITS);}this.checkCoupon(couponDO,loginUser.getId());//构建领券记录CouponRecordDO couponRecordDO = new CouponRecordDO();BeanUtils.copyProperties(couponDO,couponRecordDO);couponRecordDO.setCreateTime(new Date());couponRecordDO.setUseState(CouponStateEnum.NEW.name());couponRecordDO.setUserId(loginUser.getId());couponRecordDO.setUserName(loginUser.getName());couponRecordDO.setCouponId(couponDO.getId());couponRecordDO.setId(null);int row = couponMapper.reduceStock(couponId);if(row == 1){couponRecordMapper.insert(couponRecordDO);}else{log.info("发送优惠券失败:{},用户:{}",couponDO,loginUser);}
}

 

加个synchronized关键字,这样每个请求都得排队执行这个扣减库存操作,可以一定程度解决并发安全问题,但由于synchronized关键字基于jvm级别加锁,当集群环境下,有多个jvm进程,所以这种方法仅适用于单机节点。

Sql版本号

update product set stock=stock-1 where stock=#{上一次的库存}  and id = #{id} and stock>0

这种方法有个ABA的问题,我们可以加个version字段,每次修改数据的时候这个字段会加 1,这样就可以避免 ABA 问题。但是这种依靠数据库进行并发安全保障,会消耗数据库的资源,一定请求量内(需经过严格测试)可使用。

     update product set stock=stock-1,versioin = version+1 where   #{id} and stock>0 and version=#{上一次的版本号}

Redis分布式锁

引入 Redis 后,当领取优惠券时会先去 Redis 里面去获取锁,当锁获取成功后才可以对数据库进行操作。

在分布式锁中我们应该考虑如下:

排他性,在分布式集群中,同一个方法,在同一个时间只能被某一台机器上的一个线程执行;
容错性,当一个线程上锁后,如果机器突然的宕机,如果不释放锁,此时这条数据将会被锁死;
还要注意锁的粒度,锁的开销;
满足高可用,高性能,可重入。
伪代码如下:

@RestController
public class IndexController {public static final String REDIS_LOCK = "coupon_lock";@AutowiredStringRedisTemplate template;@RequestMapping("/getCoupon")public String getCoupon(){// 每个人进来先要进行加锁,key值为"good_lock"String value = UUID.randomUUID().toString().replace("-","");try{// 为key加一个过期时间Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L,TimeUnit.SECONDS);// 加锁失败if(!flag){return "抢锁失败!";}System.out.println( value+ " 抢锁成功");String result = template.opsForValue().get("coupon:001");int total = result == null ? 0 : Integer.parseInt(result);if (total > 0) {// 在此处需要处理抢购优惠券业务,处理时间较长。。。int realTotal = total - 1;template.opsForValue().set("coupon:001", String.valueOf(realTotal));System.out.println("获取优惠券成功,库存还剩:" + realTotal + "件, 服务端口为8001");return "获取优惠券成功,库存还剩:" + realTotal + "件, 服务端口为8001";} else {System.out.println("获取优惠券失败,服务端口为8001");}return "获取优惠券失败,服务端口为8001";}finally {// 谁加的锁,谁才能删除,使用Lua脚本,进行锁的删除Jedis jedis = null;try{jedis = RedisUtils.getJedis();String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +"then " +"return redis.call('del',KEYS[1]) " +"else " +"   return 0 " +"end";Object eval = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));if("1".equals(eval.toString())){System.out.println("-----del redis lock ok....");}else{System.out.println("-----del redis lock error ....");}}catch (Exception e){}finally {if(null != jedis){jedis.close();}}}}
}

Redission 红锁

Redission红锁其实是上述redis分布式锁的升级版,主要是框架已经封装好了我们需要的方法,实际过程中只要引入相应的jar包,使用对应的api即可。

Maven引入:

   org.redisson
   redisson
   3.17.4
伪代码如下:

public JsonData getCoupon(long couponId, CouponCategoryEnum categoryEnum) {String key = "lock:coupon:" + couponId;RLock rLock = redisson.getLock(key);LoginUser loginUser = LoginInterceptor.threadLocal.get();rLock.lock();try{//业务逻辑}finally {rLock.unlock();}return JsonData.buildSuccess();
}

使用这种方式也无需关心 key 过期时间续期的问题,因为在 Redisson 一旦加锁成功,就会启动一个 watch dog,你可以将它理解成一个守护线程,它默认会每隔 30 秒(可灵活配置)检查一下,如果当前客户端还占有这把锁,它会自动对这个锁的过期时间进行延长。

Zookeeper分布式锁

Zookeeper分布式锁应用了临时顺序节点。具体如何实现呢?让我们来看一看详细步骤:

获取锁:

首先,在Zookeeper当中创建一个持久节点ParentLock。当第一个客户端(Client1)想要获得锁时,需要在ParentLock这个节点下面创建一个临时顺序节点 Lock1。

之后,Client1查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock1是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。

 

这时候,如果再有一个客户端 Client2 前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock2。

 

Client2查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock2是不是顺序最靠前的一个,结果发现节点Lock2并不是最小的。

于是,Client2向排序仅比它靠前的节点Lock1注册Watcher,用于监听Lock1节点是否存在。这意味着Client2抢锁失败,进入了等待状态。

这时候,如果又有一个客户端Client3前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock3。

 

Client3查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock3是不是顺序最靠前的一个,结果同样发现节点Lock3并不是最靠前的。

于是,Client3向排序仅比它靠前的节点Lock2注册Watcher,用于监听Lock2节点是否存在。这意味着Client3同样抢锁失败,进入了等待状态。

这样一来,Client1得到了锁,Client2监听了Lock1,Client3监听了Lock2。这恰恰形成了一个等待队列,很像是Java当中ReentrantLock所依赖的AQS。

怎么测试并发安全问题?

首先我们要保证测试环境的项目是分布式、集群部署,其次可以根据线上获取优惠券接口的实际QPS,在测试环境使用工具jmeter并发请求优惠券接口,运行一段时间后,再去看下数据库相应的数据,譬如优惠券库存信息,抢购优惠券信息等等,反复多次运行看下效果。

总结

本篇文章主要分享了电商项目中一种常见的并发安全问题,以及相应的解决方案,如果从性能的角度去考虑应该是Redis > zookeeper > 数据库。从可靠性(安全)性角度:zookeeper > Redis > 数据库。
下面是配套学习资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

软件测试面试小程序

被百万人刷爆的软件测试题库!!!谁用谁知道!!!全网最全面试刷题小程序,手机就可以刷题,地铁上公交上,卷起来!

涵盖以下这些面试题板块:

1、软件测试基础理论 ,2、web,app,接口功能测试 ,3、网络 ,4、数据库 ,5、linux

6、web,app,接口自动化 ,7、性能测试 ,8、编程基础,9、hr面试题 ,10、开放性测试题,11、安全测试,12、计算机基础

文档获取方式:

这份文档,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

k8s-2 集群升级

首先导入镜像到本地 然后上传镜像到仓库 在所有集群节点 部署cri-docker k8s从1.24版本开始移除了dockershim,所以需要安装cri-docker插件才能使用docker 配置cri-docker 升级master 节点 升级kubeadm 执行升级计划 修改节点套接字 腾空节点 升级kubelet 配置k…

github 网页显示不全?

问题 解决 1、检查网页,打开 network,重新刷新 github 网页 2、查看无法加载的资源(如 css 文件) 3、查看域名地址 https://tool.chinaz.com/dns/,github.githubassets.com(检查网页元素,点击无…

【从0学习Solidity】 6. 引用类型, array, struct

【从0学习Solidity】 6. 引用类型, array, struct 博主简介:不写代码没饭吃,一名全栈领域的创作者,专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构,分享一些项目实战经验以及前沿技术的见解。关注我们的主页&…

一百八十二、大数据离线数仓——离线数仓从Kafka采集、最终把结果数据同步到ClickHouse的完整数仓流程(待续)

一、目的 经过6个月的奋斗,项目的离线数仓部分终于可以上线了,因此整理一下离线数仓的整个流程,既是大家提供一个案例经验,也是对自己近半年的工作进行一个总结。 二、项目背景 项目行业属于交通行业,因此数据具有很…

《信息系统项目管理师教程(第4版)》第1章至第5章 信息化发展、信息技术发展、信息系统治理、信息系统管理、信息系统工程 常见考点、知识点、思维导图、xmind

第一章至第五章多以选择题形式考察,分值在20分左右。已将考点、知识点整理成思维导图,可免费下载。以下是思维导图的部分截图: 第一章 信息化发展 第二章 信息技术发展 第三章 信息系统治理 第四章 信息系统管理 第五章 信息系统工程

【100天精通Python】Day67:Python可视化_Matplotlib 绘动画,2D、3D 动画 示例+代码

1 绘制2D动画(animation) Matplotlib是一个Python绘图库,它提供了丰富的绘图功能,包括绘制动画。要绘制动画,Matplotlib提供了FuncAnimation类,允许您创建基于函数的动画。下面是一个详细的Matplotlib动画示…

SpringCloud Gateway--网关服务基本介绍和基本原理

😀前言 本篇博文是关于SpringCloud Gateway的基本介绍,希望你能够喜欢 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满意是我的动力…

Postman应用——测试脚本Test Script

文章目录 Test Script脚本CollectionFolderRequest 解析响应体断言测试 测试脚本可以在Collection、Folder和Request的Pre-request script 和 Test script中编写,测试脚本可以检测请求响应的各个方面,包括正文、状态代码、头、cookie、响应时间等&#x…

Vue脚手架一站式搭建项目

一、什么是vue-cli 1.1如果你只是简单写几个Vue的Demo程序,那么你不需要VueCLI脚手架。 1.2.如果你在开发大型项目,那么你需要,并且必然需要使用VueCLI。 1.2.1使用Vue.js开发大型应用时,我们需要考虑代码目录结构、项目结构和…

安全基础 --- nodejs沙箱逃逸

nodejs沙箱逃逸 沙箱绕过原理:沙箱内部找到一个沙箱外部的对象,借助这个对象内的属性即可获得沙箱外的函数,进而绕过沙箱 前提:使用vm模块,实现沙箱逃逸环境。(vm模式是nodejs中内置的模块,是no…

凹凸贴图和法线贴图的区别

1、什么是凹凸贴图 凹凸贴图(bump mapping)是一种计算机图形学中的渲染技术,用于在给定的表面上模拟微小的凹凸纹理。通过在表面法线方向上微调每个像素的光照值,可以给平滑的表面增加视觉上的凹凸感。 在凹凸贴图中,每…

DockerKubernetes ❀ Service下Port端口区分

文章目录 概述案例 概述 在Kubernetes中,Service(svc)是一种抽象机制,用于将一组 Pod 暴露给其他应用程序或服务。Service 可以有三种类型的端口: nodePort:这是 Service 在节点上公开的端口。可以使用此…

基于微信小程序的校园生活管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境学生微信端的主要功能有:管理员的主要功能有:具体实现截图视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝1…

软考考试多少分算通过?

软考证书取得需要达到总分45分,每门科目满分为75分。因此,不要小看45分,在考试中获得这个分数并不容易。此外,软考要求一次性通过,如果没有通过,成绩将不被保留。因此,必须在一次考试中成功通过…

服务网格和性能优化:介绍如何通过服务网格提高微服务架构的性能和可扩展性

🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…

vue3项目学习一:创建vue3项目

创建vue3项目 一、使用vue-cli创建vue3项目1.安装vue-cli2.创建vue3项目 二、初始化项目结构三、导入element-ui 一、使用vue-cli创建vue3项目 1.安装vue-cli 先查看是否安装vue-cli 在cmd窗口输入vue -V查看版本,如果出现 则说明存在vue-cli,如果出现 则需要安…

Mac使用CMakeList编译ImGUi项目

文章目录 创建项目1.下载ImGui2.下载GLAD3.下载GLFW4.编译项目5.运行截图 创建项目 我这里创建一个demo,opengl这个是可以跨平台的,所以在mac上使用ImGui的opengl3示例 1.下载ImGui 我使用的是docking版本的,这个版本支持停靠功能&#xff…

Linux Qt5.15.2 编译QWebEngine源码支持音视频H264

背景 默认自带的QWebEngine 因版权问题不支持音视频功能,需要自己编译源码以支持。 平台:Linux(UOS V20 1050) Qt:5.15.2 准备工作 下载 Qt 5.15.2 对应版本源码,使用镜像网站或者Qt Maintenance Tool工…

计算机竞赛 深度学习YOLOv5车辆颜色识别检测 - python opencv

文章目录 1 前言2 实现效果3 CNN卷积神经网络4 Yolov56 数据集处理及模型训练5 最后 1 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 **基于深度学习YOLOv5车辆颜色识别检测 ** 该项目较为新颖,适合作为竞赛课题方向&#xff0…

一台PoE交换机可以为多少个设备提供供电?

如今在安防监控领域,许多网络设备都支持PoE供电。在网络监控工程中,为了节省布线成本并提高便捷性,大多数工程商选择使用PoE供电方案,也就是使用PoE交换机为监控摄像头提供电力。那么,一台功率输出以太网(P…