如何处理接口幂等性问题(重复提交)

接口幂等:多次请求,结果一致。

同样的请求参数,多次去访问同一个接口,得到的结果是一致的。且服务端(针对于数据入库或数据修改)只处理一次。通俗点讲就是:防止重复提交。

以下演示相关案例

  • 案例1: 数据表添加数据
  • 案例2:完成任务,领取奖励
  • 案例3:修改了其他线程已经修改的数据
  • 案例4:接口回调,且存在重试
    • 总结

案例1: 数据表添加数据

往数据库添加数据

如下数据库(user),和服务端添加(user)数据接口。
如何处理接口幂等性问题(重复提交)_第1张图片
假如用同样的参数去调用添加(user)接口,那么每次都会往数据库插入一条数据,那么数据库就会有多条重复的数据。但是该数据库(user)并没有指明某个字段是唯一的,所以对于服务端来说,每一次请求都是合理的(即使是相同的参数)。 在没有指明字段值唯一时,服务端添加数据接口,应认为所有请求都是合理的,所以不存在幂等性问题。

假如将 user表中的 user_name 字段设定为不能重复。那么如何去处理幂等性呢?

  1. 添加数据入库之前,查询user_name是否存在。
  2. 数据表(user)的user_name设置唯一索引。推荐

1:添加数据入库之前,查询user_name是否存在。

如何处理接口幂等性问题(重复提交)_第2张图片

以上代码可以解决吗?可以,但是必须是建立在单线程,或没有并发的情况。
为什么必须建立在单线程基础上,或没有并发的情况呢?

假设有多个请求同时到达添加用户接口(save),又同时去数据库中查询(user_name)是否存在,如果user_name不存在,则就会同时添加多条数据,造成(user_name)重复。

如何在以上代码上改进解决?在接口方法上加synchronized或者使用Lock锁。
如何处理接口幂等性问题(重复提交)_第3张图片
这种方案一定不可取,这会导致该接口变成单线程。同时也存在一个的问题,那就是服务器集群环境下,锁只针对当前服务器,如果多个线程分发给多个服务器去处理,那么同样又重复数据。

那么分布式锁可以解决吗?可以的

注意:如果非要用分布式锁解决的话,一定要将锁的粒度细化,用user_name作为锁的key。


2:数据表(user)的user_name设置唯一索引。推荐
如何处理接口幂等性问题(重复提交)_第4张图片
代码修改成以下这样
如何处理接口幂等性问题(重复提交)_第5张图片
为什么要将user_name设置唯一索引

在最终数据持久化(数据库)中,能够非常好的保证数据唯一,同时也能在编码中减少许多代码判断。


案例2:完成任务,领取奖励

相信有不少朋友做过完成活动,然后领取积分,红包,啥的,不知道有没有碰到过多次领取,都领取成功的情况。

假设现在有个需求:用户完成任务,领取1元现金红包

  1. 用户在任务列表领取任务,领取后状态是待完成
  2. 用户完成任务后,状态是领取奖励
  3. 用户领取完奖励后,状态是已领取

现在要实现的是:

  1. 用户领取任务,只能领取一次,且重复领取 数据记录表中只能有一条记录。
  2. 用户完成任务后,领取奖励,重复领取情况下,只能发放奖励一次。

先建立表,以下表揭是模拟表

任务表:
如何处理接口幂等性问题(重复提交)_第6张图片
用户任务记录表:
如何处理接口幂等性问题(重复提交)_第7张图片

1:用户领取任务,只能领取一次,且重复领取 数据记录表中只能有一条记录

这个实现方式与案例1一致,需求可以得知 用户id任务id用户任务记录表中是唯一的,所以可以设置一个组合字段的唯一索引。
领取任务的先查询下任务是否领取,然后再添加记录。
如何处理接口幂等性问题(重复提交)_第8张图片

2:用户完成任务后,领取奖励,重复领取情况下,只能发放奖励一次

  1. 查询任务状态是否已完成或者已领取
  2. 发放奖励前,采用用户id+任务id组合获取分布式锁。
  3. 检查下数据表,任务状态是否已领取,双重检查。如果不想双重检查可以在接口处获取分布式锁。重要
  4. 修改任务状态为已领取重要
  5. 发放奖励后,必须写入至用户奖励流水表,并备注因何获得。重要

伪代码 就不贴了。

为什么要使用分布式锁?

因为是涉及的金额之类的,如果出错,就会导致财产损失,以后对账都不好对。加了分布式锁之后,就会变成单线程去执行发放奖励,且锁的粒度也小,不会导致整个接口变成单线程。

前端可以控制重复提交吗? 可以

用户在点击领取奖励按钮后,在未返回结果的情况下,按钮不可再点击。

前端控制了重复提交,服务端可以不用去控制吗? 不可以

服务端是整个数据添加的入口,前端只是调用服务端接口的一个应用而已。前端控制了,还有其他渠道也可以访问服务端接口。

案例3:修改了其他线程已经修改的数据

说明:

  1. 线程A 读取了数据库一行数据。
  2. 线程B 读取了线程A同一行数据。
  3. 线程A 把数据修改了。
  4. 线程B 把数据也修改了,或者改回去了。
  5. 导致线程A 白忙活了。

此问题我并未有遇到过实际情况,所以不好举例。
因为是修改数据,我都认为每个线程都是合理的,最终数据以最后修改为准。

如何解决:数据表中 每行数据版本机制

  1. 线程A 读取到了数据 假设当前行数据的版本是 version = 1。
  2. 线程B 读取的数据同线程A一致。
  3. 线程A 修改数据的Sql = set version = version + 1 where version = 1。
  4. 线程B 修改数据的Sql = set version = version + 1 where version = 1。
  5. 因为两条Sql的 where条件都是 version = 1,所以只有一个线程修改成功。谁先修改,谁成功。

案例4:接口回调,且存在重试

做过三方支付,或者提供接口给别人对接,应该都知道这个幂等性问题。

举个例子:
假如用户支付成功后,就加相应的积分。用三方支付,支付完后,都会被三方进行回调,假如三方回调我们的接口,出错了,或超时了,三方会继续重试,那就会导致多次调用接口。这也是重复提交。


伪代码:假设我们的服务器应该某些原因,没有及时响应给三方调用者,那么就会触发重试,然后又给用户增加了积分。
如何处理接口幂等性问题(重复提交)_第9张图片
如何改进与设计

  • 获取此次接口请求的订单号,这是唯一的。
  • 查询该订单是否已经处理,如果处理了,响应成功。
  • 记录该订单号已经处理了。
  • 异步去处理给用户添加积分。
  • 响应给三方成功。

伪代码
如何处理接口幂等性问题(重复提交)_第10张图片

为什么已经处理该订单 也要响应成功

这可以说是幂等性的核心理念吧,多次请求,结果一致,响应结果也是一致



总结

幂等性就是在对一些唯一的数据,做校验,当服务器多次接收到这些唯一数据时,做合适的处理。但是响应是同样的结果,都是成功。

重点就是在唯一的数据上做处理。

你可能感兴趣的:(笔记,api,幂等性)