如何保证接口幂等性?

一、什么是接口幂等性?

所谓的接口幂等性就是,针对同一个接口,多次发出同一个请求,必须保证操作只执行一次。

系统中一些核心的接口如果没有实现接口幂等性会有很严重的后果,比如:

  • 下单接口,同一个订单可能会创建多次,导致出现多张相同的订单
  • 支付接口,重复支付会导致多次扣款

幂等性问题多发生于新增操作和修改操作当中:

  • insert操作:这种情况下多次请求,可能会产生重复数据。
  • update操作:这里要分两种情况,一种是单纯的更新数据,比如:update order set status=0 where id = 1;,这种是没有问题的,无论执行成功多少次状态都是一致的,是幂等操作,但是如果你的更新sql里面还包含着计算逻辑,比如:update order set num = num+1 where id =1;,对于这种sql,如果多次执行,那就会导致数据错误了。

二、幂等性的解决方案

对于这个问题,我们有两个方向,一是在客户端浏览器做防止重复提交的处理,比如按钮置灰、加loading状态,但是这种方式不绝对可靠,比如别人可以直接通过类似postman这种工具来对后端发起请求,直接跳过你前端做的限制;另一个是在服务端做是否重复提交的校验。

接下来我们进行详细说明:

1.使用唯一索引来防止新增脏数据

利用数据库唯一索引机制,当数据重复时,插入数据库会抛出异常,保证不会出现脏数据。

2.乐观锁

这里的乐观锁指的是用乐观锁的原理去实现,在数据表中新增一个version字段,通过version字段来做乐观锁,当数据需要更新时,先去数据库里获取此时的version版本号,如:

sql 复制代码
select version from test where id = 1;

更新数据时首先跟查出来的版本号去做对比,如果不相等,说明在此之前已经有其他的请求去更新过数据了,提示更新失败。

sql 复制代码
update test set status=status+1,version=version+1 where id=1 and version=#{version}

更新的时候会同时将version字段进行加1。

3.加悲观锁

在获取数据时进行加锁,当同时有多个重复请求时其他请求无法进行操作。

下面进行举例说明,比如用户下订单,进行支付,用户的账号里面有200元,需要支付100元,正常情况下用户的账号支付完成后还会有100元。sql如下:

sql 复制代码
update account set amount =  amount - 100 where id = 1;

如果出现多次相同的请求,就会导致该用户的余额变成负数。此时我们可以使用悲观锁,将该用户的那行数据进行锁住,在同一时刻只允许一个请求获得锁,更新数据,其它请求需要等待。

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

sql 复制代码
select * from account where id  = 1 for update;

下面我们画一下流程图,更直观一点:

注意:这里的id字段必须是主键或者唯一索引,不然会锁住整张表。(FOR UPDATE是否会锁表取决于查询条件是否使用了索引或主键:如果条件使用了索引或主键,则通常为行锁;否则可能升级为表锁。‌‌)

4.建立防重表

以支付功能为例: 使用唯一主键去做防重表的唯一索引,比如使用订单号作为防重表的唯一索引,每一次请求都根据订单号向防重表中插入一条数据,插入成功说明可以处理后面的业务,当处理完业务逻辑之后删除防重表中的订单号数据,后续如果有重复请求,则会因为防重表唯一索引原因导致插入失败,直接返回操作失败,可以看出防重表作用就是加锁的功能。

5.token机制

这种方案需要两次请求才能完成一次业务操作:

1.第一次请求是去获取token的。

2.第二次请求带着这个token去完成后续的业务操作。

举个例子来说明:

  • 在进入到提交订单页面之前,需要调用获取token的接口,然后将token保存到redis当中。
  • 支付的时候带上token,后端检查redis当中是否存在该token,如果存在,表示第一次发起支付请求,删除缓存中的token开始处理支付逻辑;如果缓存中不存在,说明之前已经支付过了,返回错误。
相关推荐
贰先生6 分钟前
Xiuno BBS 审计之问题16:get_magic_quotes在 PHP 8.0 被移除
后端
爱跳舞的烤冷面12 分钟前
自学嵌入式第N天(Linux篇——文件编程)
java·开发语言·前端
IT_陈寒18 分钟前
Vite的热更新突然失效,原来我忽略了这个配置
前端·人工智能·后端
怪奇云呼军20 分钟前
闪电智能VoiceAgent 如何管理呼入、接听、桥接和挂断状态?
java·前端·网络·数据库·人工智能
云浪22 分钟前
Milvus + RAG 实战:《红楼梦》问答助手
前端·人工智能·后端
Escalating_xu28 分钟前
【Linux】进程间通信:从匿名管道、FIFO 到共享内存、消息队列与信号量
java·linux·面试
IT机器猫44 分钟前
RabbitMQ基础二
java·spring boot·分布式·spring cloud·log4j·rabbitmq·springamqp
阿部多瑞 ABU1 小时前
告别手工核图:基于 .NET + MuPDFCore 的 CAD 等轴测 PDF 材料表提取与新旧版本对比实战
后端·算法·ui·pdf·c#
卷心菜的学习路1 小时前
基于 Spring Cloud Tencent 元数据路由实现个人开发泳道,告别频繁部署
java·spring cloud
卷无止境1 小时前
FastAPI 的 WebSocket 装了些什么
后端·python·fastapi