写点什么

接口的幂等性的多重考虑,你会了吗?

用户头像
moon聊技术
关注
发布于: 2020 年 11 月 21 日
接口的幂等性的多重考虑,你会了吗?

目录


[TOC]


前言


    今天的主题:接口幂等性的解决方案。本来是想把对象的存储过程和内存布局肝出来的,但是临时产生了变化,哈哈,这部分内容我们留在下一期吧,有句话说的好,好事多磨,对吧。</br>


    在实际项目开发中接口是我们在开发中经常接触到的,而且是经常经常要写,每一个项目可能都会伴随着大量的接口开发,在 moon 来涂鸦的这几个月,基本上就是在与接口作斗争了,新需求除了业务相关就是设计表和接口编写了。</br>


    当然,在接口设计中我们要考虑很多问题,安全性,格式,设计等等,今天我们先来聊聊,在高并发环境下,接口幂等性的解决方案有哪些。


正文


1 接口幂等性


     就是说在多次相同的操作下保证最终的结果是一致的。


    其实这个概念还是比较简单的,很容易理解,那我们思考一个问题,如果不保证接口幂等性会有什么问题


1.1 案例


    我们简单的举个例子,现在有一个接口,提供了转账的功能,a 要给 b 转账 1000 元,正常情况下我们接口一次性就调用成功了,但是却因为网络抖动等其它原因没有成功,于是就开始不停的重试,突然网络好了,但是这时却连续发出去了三个请求,但是这个接口没有保证幂等性,于是从结果上来看就是 a 给 b 转了 3000 元,这显然是程序业务逻辑上不能接受的(其实 moon 可以当 b 的)。


2 解决方案


2.1 token 机制


    token 机制其实是比较简单的,我们先来简单的说一下流程。</br>


  • 首先客户端先请求服务端,服务端生成 token,每次请求生成的都是一个新的 token(这个 token 一定要设置超时时间),将 token 存入 redis 当中,然后将 token 返回给客户端。

  • 客户端携带刚刚返回的 token 请求服务端做业务请求

  • 服务端收到请求,做判断。如果 token 在 redis 中,则直接删除该 token,然后继续做业务请求。如果 token 不在 redis 中,代表已经执行过当前业务了,则不执行业务。


    图示如下:



    token 机制实现方式还是比较简单的,但是其实对于我们某些响应速度要求很高的业务不太友好,缺点就是需要多一次请求获取 token 的过程


    正常来说是每次请都会生成一个新的 token,如果有极限情况下,有两个请求都带着相同的 token 进来,会存在都走入判断是否存在的过程,可能都会同时查到存在,这样也会有问题,针对这种情况,我们可以在删除前判断下是否存在,存在就删除,为了保证原子性,这部分逻辑建议使用 lua 脚本完成


2.2 去重表


    去重表的机制是根据 mysql 唯一索引的特性来的,我们先来说下它的流程:


  • 首先客户端先请求服务端,服务端先将这次的请求信息存入一张 mysql 的去重表中,这张表要根据这次请求的其中某个特殊字段建立唯一索引,或者主键索引

  • 判断是否插入成功如果插入成功,则继续做后续业务请求。如果插入失败,则代表已经执行过当前请求。


    图示如下:



    去重表机制的问题有两点:


  • 1.mysql 容错性,也就是 mysql 本身如果不是高可用的那么业务可能会受到影响:

  • 2.既然是唯一索引,自然在写表的时候就没有办法用到 changbuffer,每次都要从磁盘查出来判断再写入,对于一个高并发的接口来说,这些都是需要考虑的因素。


2.3 redis 的 SETNX 键值


    过程如下:


  • 首先客户端先请求服务端,服务端将能代表这次请求业务的唯一字段以 SETNX 的方式存入 redis,并设置超时时间,超时时间可以根据业务权衡。

  • 判断是否插入成功如果插入成功,则继续做后续业务请求。如果插入失败,则代表已经执行过当前请求。


    这里我们是利用了 redis setnx 的特性来完成的。</br>


    setnx:只在键 key 不存在的情况下,将键 key 的值设置为 value。若键 key 已经存在,则 SETNX 命令不做任何动作。命令在设置成功时返回 1,设置失败时返回 0


    图示如下:



    这种方案可以说是针对上一个方案改进的,效率也会提高很多。


2.4 状态机幂


    这种机制适用于有不同状态的业务,moon 的上一家公司就是这样做的。


    我们的订单系统,一条订单会有多个状态,如:待付款,锁定,已付款等状态,而这些状态都是有流程和逻辑的,我们可以根据这个状态判断是否执行后续业务操作。


2.5 乐观锁(更新操作)


    就是数据库中增加版本号字段,每次更新根据版本号来判断


    过程如下:


  • 首先客户端先请求服务端,先查询出当前的 version 版本。select version from .. where ..

  • 根据 version 版本来做 sql 操作 UPDATE .. SET ... version=(version+1) WHERE .. AND version=version;


    这个图示我就不再画了,还是比较简单的


2.6 悲观锁(更新操作)


    假设每一次拿数据,都有认为会被修改,所以给数据库的行上锁,也是基于数据库特性来完成。


     当数据库执行 select for update 时会获取被 select 中的数据行的行锁,因此其他并发执行的 select for update 如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。


START TRANSACTION; # 开启事务SELETE * FROM TABLE WHERE .. FOR UPDATE;UPDATE TABLE SET ... WHERE ..;COMMIT; # 提交事务
复制代码


结语


    关于接口幂等性这部分内容,解决方案其实大同小异,很多方式的原理都是一样的,更多的其实都是在业务链路中去过滤,也会有很多是有消息中间件去解决的,默认在中间件这一层就直接过滤掉了,当然每种方式都有各自的优点和缺点,需要结合当前的业务去选择,今天的文章内容,你 get 到了吗?


    我是 moon 文章首发于我的微信公众号:哪儿来的 moon,欢迎大家关注 ! 关注后回复 666 有一线大厂面试题赠送,助你成为 offer 收割机!下一篇,真的要讲 jvm 了~ 下次见~


发布于: 2020 年 11 月 21 日阅读数: 81
用户头像

moon聊技术

关注

玩玩技术,聊聊人生,看看生活,搞搞理想 2019.03.19 加入

我是moon 文章首发于我的微信公众号:哪儿来的moon,欢迎大家关注 ! 关注后回复666 有一线大厂面试题赠送,助你成为offer收割机!

评论 (1 条评论)

发布
用户头像
我是moon
文章首发于我的微信公众号:moon聊技术,欢迎大家关注 !
有什么问题也可以在我的公众号联系我,和我直接交流~
公众号中也还有很多技术书文章,欢迎大家阅读、分享、转发~
关注后回复666 有一线大厂面试题赠送,助你成为offer收割机!
2020 年 12 月 03 日 15:53
回复
没有更多了
接口的幂等性的多重考虑,你会了吗?