如何保证接口幂等性?

一、什么是接口幂等性?

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

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

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

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

  • 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开始处理支付逻辑;如果缓存中不存在,说明之前已经支付过了,返回错误。
相关推荐
BingoGo2 小时前
PHP 8.2 vs PHP 8.3 对比:新功能、性能提升和迁移技巧
后端·php
Gz、2 小时前
Spring Boot 常用注解详解
spring boot·后端·python
用户4099322502122 小时前
PostgreSQL数据类型怎么选才高效不踩坑?
后端·ai编程·trae
GHOME2 小时前
MCP-学习(1)
前端·后端·mcp
fliter2 小时前
迈向易用的Rust
后端
_院长大人_2 小时前
阿里云云效将本地的maven相关文件批量推送到阿里云仓库以及使用
java·阿里云·maven
起风了___2 小时前
Python 自动化下载夸克网盘分享文件:基于 Playwright 的完整实现(含登录态持久化与提取码处理)
后端·python
福大大架构师每日一题2 小时前
2025-09-27:子字符串连接后的最长回文串Ⅰ。用go语言,给定两个字符串 s 和 t。你可以从 s 中截取一段连续字符(也可以不取,即空串),再从 t 中
后端