接口幂等测试

本文主要阐述,什么是幂等性,出现幂等问题的情况,怎么保证幂等,希望本文对读者有所帮助;

一、接口幂等性

所谓幂等就是多次调用方法或者接口不会改变业务状态,可以保证重复与单次调用的结果是一致的;

二、什么情况下需要考虑幂等

我们的开发中,主要操作就是CRUD,其中读取操作和删除操作是天然幂等的,我们主要着重关心的就是新增和更新操作;

幂等性的使用场景:

1.前端重复提交

如新增商品功能,点击保存按钮,前端连续多次点击保存,后端就会收到多次请求接口,如果没做好幂等就会重复创建多条记录,会出现脏数据;

这个也就是我们所说的如何防止前端重复提交的问题;

2.用户恶意进行刷单

如:用户投票功能,用户针对一个用户进行重复提交投票(通过调用接口),这样或导致接口接收到用户重复提交的投票信息,会导致投票结果与事实严重不符;

3.接口超时重试

当我们调去第三方接口的时候,有可能会因为网络等原因导致调用失败,所以我们会对接口调用添加失败重试机制(Spring可以通过@Retryable注解实现重试机制)

既然重试就可能出现重复调用接口。这时再次调用时如果没有做好幂等,就可能出现脏数据;

4.消息重复消费 

使用MQ消息中间件时,在生产端和消费端都有重试机制,也就是说同一消息可能被重复消费

测试接口幂等的场景:

1.网络波动,可能会引起重复请求;

2.用户重复操作,在操作时候可能会无意出发多次下单交易;

3.使用了失效或者超时重试机制;

4.页面重复刷新 

5.使用浏览器后退按钮重复之前的操作,导致重复提交表单;

6.使用浏览器历史记录重复提交表单;

7.浏览器重复的HTTP请求;

8.定时任务重复执行;

三、如何保证接口幂等性

初级方式:

1.插入前先判断数据是否存在;

这是最基础的,也是开发中必须要做的。会在插入或者更新前先判断下,当前这个数据库中是否已存在,如果存在则不允许重复插入,不存在则可插入。

2.前端做一些交互控制

如保存按钮,用户点击后,按钮置灰或者在loading中,不可再次点击,或者跳转到其他页面,可以防止很大部分的前端重复提交;

高并发下如何保证幂等?

1.基于悲观锁

2.基于乐观锁

3.基于状态码

4.基于唯一索引

5.基于分布式锁

6.基于token

等等方式,实际使用中基本分布式锁用的比较多;

下面我们一一介绍一下各个方法原理:

1.基于悲观锁

定义:当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发;

在高并发的情况下,会造成一个业务被执行两次的情况发生,我们可以通过悲观锁实现,也就是sql查询语句中添加for update字段。

这里要注意:

1)被锁的一行数据,某个字段要加索引,否则会锁表;如order_no,加索引;

2)悲观锁在同一事务操作过程中,锁住了一行数据。悲观锁性能不佳所以一般不建议用悲观锁做这个事情;

2.基于乐观锁

定义:乐观锁就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制。

所谓的乐观锁就是在表中新增一个version(版本号)字段。

通过版本号的方式,来控制update的操作的幂等性,用户查询出要修改的数据,系统将数据返回给页面,将数据版本号放入隐藏域,用户修改数据,点击提交,将版本号一通提交给后台,后台使用版本号作为更新条件;

注意:乐观锁能够保证的是update的操作的幂等性,如果你的update本身就是幂等操作,或者install操作那就不能用乐观锁了。

3.基于状态码

很多业务表,都是有状态的,比如订单表,一般订单有1-订单创建2-订单确认3-订单支付、 4-订单完成5-取消订单等订单流程,当我们更新订单状态

第一个请求时,成功把 订单确认 状态修改成 订单支付,sql执行结果的影响行数是1。

第二个请求时,同样想把 订单确认 状态修改成 订单支付,但是sql执行结果的影响行数为0。如果是0,那么我们直接可以返回成功了。而不需要做接下来的业务操作,以此来保证接口的幂等性。

4.基于唯一索引

一般来讲悲观锁、乐观锁、状态码作用于update操作来实现幂等,而唯一索引是针对insert操作来保证幂等。

1) 创建订单时,前端先通过接口获取订单号,再请求后端时带入订单号,订单表中订单号添加唯一索引,如果存在插入相同订单号则直接报错。

2) 消费MQ消息时,messageId是唯一的,我们可以新添加一种消费记录表,将messageId作为主键,如果重复消费那么就会存在相同的messageId,插入直接报错。

5.基于分布式锁

分布式锁实现幂等性的逻辑就是,请求过来时,先去尝试获得分布式锁,如果获得成功,就执行业务逻辑,反之获取失败的话,就舍弃请求直接返回成功。

其实前面介绍过的悲观锁,本质是使用了数据库的分布式锁,都是将多个操作打包成一个原子操作,保证幂等。但由于数据库分布式锁的性能不太好,

我们可以改用:redis或zookeeper来实现分布式锁。

具体步骤:

用户通过浏览器发起请求,服务端会收集数据,并且生成订单号code作为唯一业务字段。

使用redis的set命令,将该订单code设置到redis中,同时设置超时时间。

判断是否设置成功,如果设置成功,说明是第一次请求,则进行数据操作。

如果设置失败,说明是重复请求,则直接返回成功。

6.基于 Token

token机制的核心思想是为每一次操作生成一个唯一性的凭证,也就是token。一个token在操作的每一个阶段只有一次执行权,一旦执行成功则保存执行结果。对重复的请求,返回同一个结果。token机制的应用十分广泛。

举个例子:每次请求都拿着一张门票,这个门票是一次性的,用过一次就被毁掉了,不能重复利用。这个token令牌就相当于门票的概念,每次接口请求的时候带上token令牌,服务器第一次处理的时候去校验token,并且这个token只能用一次,如果用户使用相同的令牌请求二次,那么第二次就不处理,直接返回。

token方案的特点就是:需要两次请求才能完成一次业务的操作。

一般包括两个请求阶段:

1)客户端请求申请获取token,服务端生成token返回。

2)第二次请求带着这个token,服务端验证token,完成业务操作。

具体步骤:

用户访问页面时,浏览器自动发起获取token请求。

服务端生成token,保存到redis中,然后返回给浏览器。

用户通过浏览器发起请求时,携带该token。

在redis中查询该token是否存在,如果不存在,说明是第一次请求,做则后续的数据操作。

如果存在,说明是重复请求,则直接返回成功。

在redis中token会在过期时间之后,被自动删除。

主要token这种方案有一定的危险性,我们分析一下:

1、先删除token【先删除token令牌,再执行业务】还是后删除token【先执行业务,再删除token令牌】;

(a)、先删除可能导致,业务确实没有执行,重试还带上了之前的token,由于防重设计导致,请求还是不能执行;

(b)、后删除token问题很大,可能导致,业务处理成功,但是服务闪断,出现超时,没有删除token,别人继续重试,导致业务被执行两次;

所以我们最好设计为先删除token,如果业务调用失败,就重新获取token再次请求。

2、在这里token获取,比较和删除必须保证原子性​​

在验证token是否存在,不要用redis.get(token)之后,在用redis.del(token),这样不是原子操作在高并发情况下依然会存在幂等问题。

我们可以直接用redis.del(token)的方式:

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值