接口的幂等性的N种考虑,你知道吗?

Java后端技术

共 2949字,需浏览 6分钟

 ·

2020-12-10 05:34

往期热门文章:

1、往期精选优秀博文都在这里了!

2、如果程序员和产品经理都用凡尔赛文学对话......

3、Spring Boot 2.4.0正式发布,全新的配置文件加载机制(不向下兼容)

4、Insert into select语句引发的生产事故!

5、如果MySQL磁盘满了,会发生什么?还真被我遇到了!

目录

  • 目录
  • 前言
  • 正文
    • 1 接口幂等性
    • 1.1 案例
    • 2 解决方案
    • 2.1 token机制
    • 2.2 去重表
    • 2.3 redis 的 SETNX键值
    • 2.4 状态机幂
    • 2.5 乐观锁(更新操作)
    • 2.6 悲观锁(更新操作)
  • 结语

前言

今天的主题:接口幂等性的解决方案。本来是想把对象的存储过程和内存布局肝出来的,但是临时产生了变化,哈哈,这部分内容我们留在下一期吧,有句话说的好,好事多磨,对吧。
在实际项目开发中接口是我们在开发中经常接触到的,而且是经常经常要写,每一个项目可能都会伴随着大量的接口开发,在moon来涂鸦的这几个月,基本上就是在与接口作斗争了,新需求除了业务相关就是设计表和接口编写了。
当然,在接口设计中我们要考虑很多问题,安全性,格式,设计等等,今天我们先来聊聊,在高并发环境下,接口幂等性的解决方案有哪些。

正文

1 接口幂等性

就是说在多次相同的操作下保证最终的结果是一致的。
其实这个概念还是比较简单的,很容易理解,那我们思考一个问题,如果不保证接口幂等性会有什么问题

1.1 案例

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

2 解决方案

2.1 token机制

token机制其实是比较简单的,我们先来简单的说一下流程。
  • 首先客户端先请求服务端,服务端生成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 的特性来完成的。
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到了吗?
往期热门文章:

1、历史文章分类导读列表!精选优秀博文都在这里了!》

2、你以为JDK8之后用HashMap就没事了?死循环问题依然存在!
3、14 个 Spring MVC 顶级技巧,随时用随时爽,一直用一直爽
4、交公粮了:十一在家我都逛了哪些技术网站?
5高并发和海量数据下的 9 个 Redis 经典案例剖析!

6Docker 禁止被列入美国“实体名单”的国家、企业、个人使用

7、日志框架到底是Logback 还是 Log4j2?
8、IDEA 2020.2 重磅发布,动画级新功能预览!
9、数据库链接池终于搞对了,这次直接从100ms优化到3ms!

10、互联网公司忽悠员工的黑话,套路太深了。。。
浏览 52
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报