实用-接口幂等性校验方法

3,391 阅读4分钟

什么是接口幂等性

幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次

订单接口, 不能多次创建订单

支付接口, 重复支付同一笔订单只能扣一次钱

支付宝回调接口, 可能会多次回调, 必须处理重复回调

普通表单提交接口,多次点击提交, 只能成功一次,表中产生了两条重复的数据,只是id不一样

解决方法

1.唯一索引 -- 防止新增脏数据

2.token机制 -- 防止页面重复提交

3.悲观锁 -- 获取数据的时候加锁(锁表或锁行)

4.乐观锁 -- 基于版本号version实现, 在更新数据那一刻校验数据

5.分布式锁 -- redis(jedis、redisson)或zookeeper实现

6.状态机 -- 状态变更, 更新数据时判断状态(状态有一定的顺序)

实现

insert前先select (新增操作幂等性)

保存操作之间,进行数据查询,有就修改,没有就新增。

缺点:需要查询一次数据,麻烦

加悲观锁 (修改操作幂等性)

 在支付场景中,用户A的账号余额有150元,想转出100元,正常情况下用户A的余额只剩50元。一般情况下,sql是这样的:

update user amount = amount-100 where id=123;

如果出现多次相同的请求,可能会导致用户A的余额变成负数。

 加悲观锁,将用户A的那行数据锁住,在同一时刻只允许一个请求获得锁,更新数据,其他的请求则等待。

通常情况下通过如下sql锁住单行数据:

select * from user id=123 for update;

缺点:有性能问题,需要等待A操作完,这行数据 才能操作

加乐观锁 (修改操作幂等性)

乐观锁。需要在表中增加一个timestamp或者version字段,这里以version字段为例。

  在更新数据之前先查询一下数据:

select id,amount,version from user id=123;

如果数据存在,假设查到的version等于1,再使用id和version字段作为查询条件更新数据

update user set amount=amount+100,version=version+1where id=123 and version=1;

 更新数据的同时version+1,然后判断本次update操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。

  由于第一次请求version等于1是可以成功的,操作成功后version变成2了。这时如果并发的请求过来,再执行相同的sql

唯一索引

绝大数情况下,为了防止重复数据的产生,我们都会在表中加唯一索引,这是一个非常简单,并且有效的方案。

   alter table order add UNIQUE KEY un_code (code);

  加了唯一索引之后,第一次请求数据可以插入成功。但后面的相同请求,插入数据时会报Duplicate entry '002' for key 'order.un_code异常,表示唯一索引有冲突。

建防重表

有时候表中并非所有的场景都不允许产生重复的数据,只有某些特定场景才不允许。这时候,直接在表中加唯一索引,显然是不太合适的。

  针对这种情况,我们可以通过建防重表来解决问题。

  该表可以只包含两个字段:id和唯一索引,唯一索引可以是多个字段比如:name、code等组合起来的唯一标识,例如:susan_0001。

防重表和业务表必须在同一个数据库中,并且操作要在同一个事务中。

根据状态值

比如订单表中有:1-下单、2-已支付、3-完成、4-撤销等状态。如果这些状态的值是有规律的,按照业务节点正好是从小到大,我们就能通过它来保证接口的幂等性。

update order set status=3 where id=123 and status=2;

加分布式锁 + 获取token

redis/zk 实现 (传入的token需要验证,token是否是伪造的

为需要保证幂等性的每一次请求创建一个唯一标识token, 先获取token, 并将此token存入redis, 请求接口时, 将此token放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token:

如果存在, 正常处理业务逻辑, 并从redis中删除此token, 那么, 如果是重复请求, 由于token已被删除, 则不能通过校验, 返回请勿重复操作提示 如果不存在, 说明参数不合法或者是重复请求, 返回提示即可

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

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

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

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

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

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