接口幂等性实现

一、什么是幂等性:

  HTTP/1.1中对幂等性的定义是:一次和多次请求某一资源对于资源本身应该具有同样的结果(网络超时等问题除外)也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

    就是用户对于同一接口发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用

    即同一个接口,多次发起同一个请求,必须保证操作只能执行一次。

 

    因此在许多业务场景中,需要保证客户端的重复提交和服务端的多次重试,而资源只会产生一份最终结果。幂等机制的核心,就是保证资源唯一性。

   如:订单接口,不能创建多个订单

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

       表单重复提交,只能成功一次(保存一条记录)

       回调接口,如果出现多次回调,则要保证只处理一次,并返回处理成功

 

二、解决方案

  1、唯一索引(防止新增脏数据)

    唯一索引或唯一组合索引来防止新增数据存在脏数据

    当表存在唯一索引,并发时新增报错,说明记录已经存在了,返回成功即可。

 

  2、token机制+redis(防止页面重复提交)

    针对重复点击或者网络超时重试,或者nginx重发等情况导致数据被重复提交 

    具体实现: 

    1)服务方提供一个生成全局唯一随机数token的接口,客户端在调用业务接口前先向服务方发送请求获取token,在客户端获取token的时候,将token存入redis中。

      ① 服务方提供获取token的接口,token可以是uuid

      ② 客户端在调用业务接口前先调用接口获取token

      ③ 服务方在客户端获取token时,生成token,并将token 作为 key,客户端用户信息作为value,保存在redis中,然后返回token

      ④ 客户端请求业务接口时,将获取的token放在header(最好放在header中)或者作为请求参数请求接口

      ⑤ 服务端收到请求后,从headers中拿到token,然后根据token到redis中查找该key是否存在

      ⑥ 如果存在,业务处理成功后,从redis中删除此token。如果不存在,说明重复调用,返回请勿重复操作即可

      注意:从redis中查询token和删除token,要保证原子性,应使用redis分布式锁或者lua脚本。

  

 

    2)同样提供一个生成全局唯一随机数token的接口,客户端请求业务接口时,将获取的token放在header或者作为请求参数请求接口。

      第一次调用,业务处理成功后,将随机数作为key,操作结果作为value,存入redis,同时设置过期时长。第二次调用,查询redis,如果key存在,则证明是重复提交,直接返回错误。

 

  3、悲观锁,获取数据的时候加锁获取

  4、乐观锁

    只能适用于更新操作,可通过version版本号实现。

    为了防止重复更新,类似CAS,先比较再更新。更新时version版本号作为条件,来确定更新前没有被更新过。更新后版本号+1。

    

  5、分布式锁,redis或者zookeeper分布式锁。

  6、先查询,后插入

 

三、幂等问题判断口诀

  一锁、二判、三更新

  一锁:第一步,先加锁。可以加分布式锁、或者悲观锁都可以。但是一定要是一个互斥锁!

  二判:第二步,进行幂等性判断。可以基于状态机、流水表、唯一性索引等等进行重复操作的判断

  三更新:第三步,进行数据的更新,将数据进行持久化

  三步需要严格控制顺序,确保加锁成功后进行数据查询和判断,幂等性判断通过后再更新,更新结束后释放锁

 

  以上操作需要有一个前提,那就是第一步加锁、和第二步判断的时候,需要有一个依据,这个就是幂等号了,通常需要和上游约定一个唯一ID作为幂等号。然后通过对幂等号加锁,再通过幂等号进行幂等判断

  一锁这个过程,建议使用Redis实现分布式锁,因为他是非阻塞的高效率的互斥锁。非常适合在幂等控制场景中。

  二判这个过程,如果有操作流水,建议基于操作流水做幂等,并将幂等号作为唯一性约束,确保唯一性。如果没有流水,那么基于状态机也是可以的。

  但是不管怎么样,数据库的唯一性约束都要加好,这是系统的最后一道防线。万一前面的锁失效了,这里也能控制得住不会产生脏数据

 

 

END.

posted @ 2021-03-23 14:06  杨岂  阅读(184)  评论(0编辑  收藏  举报