并发下的幂等测试

1,509 阅读8分钟

1、什么是幂等

幂等是数学和计算机学概念。在编程中,幂等性是指一次或多次请求某一资源产生的影响相同。在数学中,幂等函数或幂等方法是指对于使用相同方法重复执行,都能获得相同结果。

2、什么是并发

并发是指在同一时间段内,同时执行多个计算程序。

2、幂等场景

幂等场景有很多,例如:

  • 前端对同一表单数据进行提交,后端只会返回一个结果
  • 用户发起一次付款,应该只能扣用户一次钱,当遇到系统bug或者网络重发,也应该只扣一次钱
  • 发送消息只发一次,同样的短信如果重复多次发送给用户,用户会崩溃
  • 创建业务订单,只能创建一次,不能创建多个订单 哪些情况下需要考虑幂等测试:
  • 重试时(网络重发、系统间重试、用户重复提交数据:前后端都需要考虑幂等)
  • 发送消息需要考虑幂等检查
  • 批量作业
  • 切库
  • 历史数据迁移

3、解决幂等的方式

唯一索引

目的:防止新增脏数据

如果确定某个列包含各不相同的值,那么可以给该列设置唯一索引。唯一索引可以保证数据的唯一性,在很多场景中,唯一索引的目的并不在于提高访问速度,更多的在于为了避免数据的重复出现。 创建唯一索引

CREATE UNIQUE INDEX uni_user_info_pass ON user_info(pass);

创建唯一符合索引

CREATE UNIQUE INDEX uni_user_info_pass ON user_info(name,pass);

悲观锁

悲观锁,顾名思义就是很悲观,每次拿数据的时候认为别人会修改,所以每次在拿数据的时候都会去上锁,这样别人在拿数据的时候就会block,直到悲观锁被释放,才能拿到锁。悲观锁中的共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。悲观锁适用于对数据进行频繁的修改和更新的场景,如银行转账、订单处理等。

悲观锁:假设发生并发冲突,屏蔽掉一切可能违反数据的操作

select * from table_name where id='XXX' for update

注意:id一般是主键或者唯一索引,不然是锁表,会出事的。 悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用。

悲观锁的实现,往往依靠数据库提供的锁机制,在数据库中,我们如何用悲观锁去解决这个事情呢?
有用户A和用户B,在同一家店铺去购买同一个商品,但是商品的可购买数量只有一个。 这个店铺的商品表t_goods结构和表中的数据: image.png

1.加入当用户A对下单购买商品(臭豆腐)的时候,先去尝试对该数据(臭豆腐)加上悲观锁
2.加锁失败:说明商品(臭豆腐)正在被其他事务进行修改,当前查询需要等待或者抛出异常,具体返回的方式需要由开发者根据具体情况去定义
3.加锁成功:对商品(臭豆腐)进行修改,也就是只有用户A能买,用户B想买(臭豆腐)就必须一直等待。当用户A买好后,用户B再想去买(臭豆腐)的时候会发现数量已经为0,那么B看到后就会放弃购买
4.在此期间如果有其他对该数据(臭豆腐)做修改或加锁的操作,都会等待我们解锁后或者直接抛出异常

乐观锁

乐观锁,顾名思义就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁,但是如果想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据。如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功(当然也允许更新失败的线程放弃操作)。乐观锁只在更新数据的时候会上锁,其他时间不锁表,所以相对于悲观锁,效率更高。因此,乐观锁适用于多读的应用类型,这样可以提高吞吐量。

乐观锁:假设发生冲突,只在提交时检查是否违反数据完整性操作

乐观锁一般来说有以下2种方式:

使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

update table_xxx set name=#name#,version=version+1 where version=#version#

分布式锁

单机多线程:  在 Java 中,我们通常使用 ReetrantLock 类、synchronized 关键字这类 本地锁 来控制一个 JVM 进程内的多个线程对本地共享资源的访问。
分布式系统:  不同的服务/客户端通常运行在独立的 JVM 进程上。如果多个 JVM 进程共享同一份资源的话,使用本地锁就没办法实现资源的互斥访问了。于是,分布式锁就诞生了。分布式锁,即分布式系统中的锁。
总结:在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。

有三种实现方式

基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁;

具体介绍可看

zhuanlan.zhihu.com/p/514351547

状态机幂

token机制

4、幂等测试关注点

  1. 需求:需求评审上多关注产品设计上是否考虑幂等
  2. 业务场景:对失败机制或者重试机制要有幂等
  3. 前端:需要关注按钮的多次快速点击、点击提交按钮两次、点击刷新按钮、使用浏览器后退按钮重复之前的操作,导致重复提交表单、使用浏览器历史记录重复提交表单
  4. 后端:就是接口的幂等测试,使用postman或jmeter多次发送同一参数的请求,查看服务端响应。

5、如何使用jmeter测试幂等

Thread Group 一个线程组可以看成是一个虚拟用户组,线程组里的每个线程都可以理解为一个虚拟用户

image.png Number of Threads (users):线程数也就是并发数,每个线程将会完全独立的运行测试计划,互不干扰
Ramp-Up Period (in seconds):启动线程所需要的时间
Loop Count:每个线程循环执行的次数

Synchronizing Timer 该功能类似loadrunner的集合点

image.png

Number of Simulated Users to Groupby: 每次释放的线程数量。如果设置为0,等于设置为线程组中的线程数量
Timeout in milliseconds: 最大等待时间,如果设置为0,该定时器将会等待线程数达到了"Number of Simultaneous Users toGroup"中设置的值才释放。如果大于0,那么如果超过Timeout inmilliseconds中设置的最大等待时间(毫秒为单位)后还没达到"Number of Simultaneous Users toGroup"中设置的值,Timer将不再等待,释放已到达的线程。默认为0

一般设置超时时间要么是0,要么超时时间 > 请求集合数量 * 1000 / (线程数 / 线程加载时间)。如下面的场景:请求集合数量=100,线程数=100,加载时间=10,根据上面的图请求集合数量=100,那么超时时间要>(100*1000/100/10=100)。

image.png

通过运行并发请求,可在查看结果树中查看并发结果

6、并发测试时,请求入参不一致。

可在Jmeter中设置CSV数据文件 image.png

在请求参数这里以${cityId}获取文件中参数 image.png