java中接口幂等性解决方案总结

article/2025/10/3 15:41:00

一、概念

一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。

二、场景

1、前端页面在填写一些表单点击提交保存按钮的时候,因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求,后端收到了好几次提交,这时就会在数据库中重复创建了多条记录,这就是接口没有幂等性带来的 bug。

2、接口恶意调用刷单,比如投票功能,针对某一个用户重复提交,会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。

3、 一个订单创建接口,第一次调用超时了,然后调用方重试了一次,虽然第一次超时了,但是实际也许创建成功了,再次调用接口重试,这个时候就会调用2次创建接口,会创建2张订单,实际我们只想创建一张订单。

4、电商系统订单消耗库存场景:在订单创建时,我们需要去扣减库存,由于种种原因接口发生了超时,调用方重试了一次,如果接口不是幂等的,就有可能减2次库存。我们重试的目的,其实只是想一次成功的请求,如果真的减去2次库存,那就不满足需求。

5、电商系统订单退款的场景:当用户发起退款,退款接口超时,长时间未返回是否退款成功的结果,退款接口调用方重试一次,结果2次的退款请求都成功了,则会给用户退2次钱。

6、使用消息中间件来处理消息队列,且手动 ack 确认消息被正常消费时。如果消费者突然断开连接,那么已经执行了一半的消息会重新放回队列。当消息被其他消费者重新消费时,如果没有幂等性,就会导致消息重复消费时结果异常,如数据库重复数据,数据库数据冲突,资源重复等。

三、解决方案

1、数据库唯一标识

在数据库唯一主键或者在相关的字段上添加唯一索引,客户端执行创建请求,调用服务端接口,后端生成布式 ID(雪花id、redis生成全局id等等方法) 充当主键或者建立唯一索引的字段值,这样才能能保证在分布式环境下 ID 的全局唯一性,后端将该条数据插入数据库中,如果插入成功则表示没有重复调用接口。如果抛出主键重复异常,则表示数据库中已经存在该条记录,返回错误信息到客户端;

2、乐观锁

建表test01,在test01表中添加一个标识字段version,初始值设为1;
在这里插入图片描述
现在需要将张继科的age + 10;
更新前先查询张继科当前的version:1;

select version from test01 where name = '张继科';
update test01 set age = age + 100,version = version + 1 where name = '张继科' and version = 1;

更新数据的同时version+1,条件加上version = 1 (当前线程查到的版本号),然后判断本次update操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。
在这里插入图片描述

由于第一次请求version等于1是可以成功的,操作成功后version变成2了。这时如果并发的请求过来,再执行相同的sql:

update test01 set age = age + 100,version = version + 1 where name = '张继科' and version = 1;

该update操作不会真正更新数据,最终sql的执行结果影响行数是0,因为version已经变成2了,where中的version = 1肯定无法满足条件。但为了保证接口幂等性,接口可以直接返回成功,因为version值已经修改了,那么前面必定已经成功过一次,后面都是重复的请求。
在这里插入图片描述
注意:乐观锁的更新操作,最好用主键或者唯一索引来更新,这样是行锁,否则更新时会锁表;索引与表锁行锁的问题,我其他文章有详细说明;

3、悲观锁

要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,当你执行一个更新操作后,MySQL会立刻将结果进行提交。

查询是否开启自动提交事务

select @@autocommit;

设置为手动提交事务

set autocommit = 0;

假如实际业务中:需要将张继科的age修改为16;
开启事务:

begin;

查询并锁当前行;注意:name字段上有索引;

select * from test01 where name = '张继科' for UPDATE;

执行业务,张继科的年龄改为16

UPDATE test01 set age = 16 where name =  '张继科';

注意:暂时先不执行: commint

在这里插入图片描述

模拟另一个线程(新建一个查询窗口):
#执行业务,张继科的年龄改为32
UPDATE test01 set age = 32 where name = ‘张继科’;
在这里插入图片描述
它会一直处于阻塞状态;
在这里插入图片描述
直到刚才修改age为16的线程提交(commint),才会释放;其他线程才能更新操作;不影响查询操作;我们现在把刚才修改age为16的线程commint:更新成功;其他线程可以正常更新;

需要特别注意的是:如果使用的是mysql数据库,存储引擎必须用innodb,因为它才支持事务。此外,这里name字段一定要建立索引,不然会锁住整张表。悲观锁需要在同一个事务操作过程中锁住一行数据,如果事务耗时比较长,会造成大量的请求等待,影响接口性能。此外,每次请求接口很难保证都有相同的返回值,所以不适合幂等性设计场景,但是在防重场景中是可以的使用的。在这里顺便说一下,防重设计 和 幂等设计,其实是有区别的。防重设计主要为了避免产生重复数据,对接口返回没有太多要求。而幂等设计除了避免产生重复数据之外,还要求每次请求都返回一样的结果。

4.Token机制

客户端在调用接口的时候向后台请求一个全局id(token),请求的时候就携带这个全局id传到后台,后端对这个token作为key,用户信息(sessionId)作为value,以键值对的方式在redis中进行校验,如果key相同且value匹配则删除,然后执行删除操作(存的是需要设置失效时间,删除时候注意原子性操作),否则属于重复提交;

a、服务端提供生成token的接口,注意全局唯一;
b、客户端调用接口获取token,同时后端将token放到redis中,token作为key,用户信息为value;
c、客户端将获取到的token放到当前表单隐藏域中;
d、客户端在执行提交表单时,把 token 存入到 Headers 中,执行业务请求带上该 Headers;
e、服务端收到请求后,从header中拿到token,根据key在redis中查找是否存在;
f、服务端根据 Redis 中是否存该 key 进行判断,如果存在就将该 key 删除,然后正常执行业务逻辑。如果不存在就抛异常,返回重复提交的错误信息。

注意:在并发情况下,执行 Redis 查找数据与删除需要保证原子性,否则很可能在并发下无法保证幂等性。其实现方法可以使用分布式锁或者使用 Lua 表达式来注销查询与删除操作

5.分布式锁

在业务系统插入数据或者更新数据,先获取锁,获取到锁,就继续后面的业务逻辑。如果没有获取到锁,就等待锁的释放直到获取锁,当执行完业务逻辑时,释放锁,当然,锁要设置超时时间,防止意外没有释放到锁,它可以用来解决分布式系统的幂等性,布式锁类似于防重表,将防重并发放到了缓存中,较为高效,同一时间只能完成一次操作,常用的分布式锁实现方案是redis和zookeeper等;目前redision是最常用的,它不需要我们过于考虑原子操作,它包含了常用锁的类型,基本的可重入锁,读写锁,以及CountDownLatch的设置及使用,redisson的作者就是在加锁和解锁的执行层面采用Lua脚本,有原子性保证;总之它很轻大,我会在后面总结关于分布式锁的详细内容。

四、总结

幂等性应该是合格程序员的一个基因,在设计系统时,是首要考虑的问题,尤其是在像支付宝,银行,互联网金融公司等涉及的都是钱的系统,既要高效,数据也要准确,所以不能出现多扣款,多打款等问题,这样会很难处理,用户体验也不好。另外,幂等性是为了简化客户端逻辑处理,能放置重复提交等操作,但却增加了服务端的逻辑复杂性和成本,其主要是:并行执行的功能改为串行执行,降低了执行效率。增加了额外控制幂等的业务逻辑,复杂化了业务功能;所以在使用时候需要考虑是否引入幂等性的必要性,根据实际业务场景具体分析,除了业务上的特殊要求外,一般情况下不需要引入的接口幂等性,


http://chatgpt.dhexx.cn/article/nOUT1o5g.shtml

相关文章

高并发下接口幂等性解决方案

一、背景 我们实际系统中有很多操作,是不管做多少次,都应该产生一样的效果或返回一样的结果。 例如1. 前端重复提交选中的数据,应该后台只产生对应这个数据的一个反应结果;2. 我们发起一笔付款请求,应该只扣用户…

接口幂等性测试

前言 所谓幂等: 多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。 我们在开发中主要操作也就是CURD,其中读取操作和删除操作是天然幂等的,我们所关心的就是创建操作、更新操作。 创建操作一定是非幂等的因为要涉及到…

接口幂等性是什么?如何设计?

接口幂等 什么是接口幂等?为什么接口需要幂等性设计前端重复提交表单黑客恶意攻击接口超时重复提交消息重复消费 哪些接口需要幂等?如何实现幂等前端拦截数据库唯一索引实现数据库乐观锁实现数据库悲观锁实现JVM锁实现分布式锁实现Token实现 总结 接口幂…

开发中是如何保证接口幂等性的?

文章目录 一、定义二、业务场景三、保证幂等性常用方法方案1: insert前先select(基于mysql的分布式锁)方案2:加悲观锁 select * from table where id 1 for update(基于mysql的分布式锁)方案3:加乐观锁 增…

如何保证接口的幂等性?常见的实现方案有哪些?

什么是幂等性 幂等用于表示任意多次请求均与一次请求执行的结果相同,也就是说对于一个接口而言,无论调用了多少次,最终得到的结果都是一样的。 如何保证接口的幂等性 1、前端拦截 2、使用数据库实现幂等性 3、使用 JVM 锁实现幂等性 4、…

接口幂等性常见的解决方案

一、什么是接口幂等性? 接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。 举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的…

接口幂等性解决方案

一、分布式锁解决方案 先说这种方案,在网上有一些文章说可以通过分布式锁来保证幂等性。但是我认为这种方案不能保证幂等性,不可取。看下面分析 ①、方案流程介绍 用户通过浏览器发起请求,服务端接收请求数据,并且从请求数据中…

什么是接口的幂等性,如何实现接口幂等性?一文搞定

微信搜索《Java鱼仔》,每天一个知识点不错过 每天一个知识点 什么是接口的幂等性,如何实现接口幂等性? (一)幂等性概念 幂等性原本是数学上的概念,用在接口上就可以理解为:同一个接口&#x…

什么是幂等性?四种接口幂等性方案详解

幂等性在我们的工作中无处不在,无论是支付场景还是下订单等核心场景都会涉及,也是分布式系统最常遇见的问题,除此之外,也是大厂面试的重灾区。 知道了幂等性的重要性,下面我就详细介绍幂等性以及具体的解决方案&#…

接口的幂等性

1、接口调用存在的问题 现如今我们的系统大多拆分为分布式 SOA,或者微服务,一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务,而服务调用服务无非就是使用 RPC 通信或者 restful。既然是通信,…

如何保证接口的幂等性?

什么是幂等性?所谓幂等,就是任意多次执行所产生的影响均与一次执行的影响相同。 为什么会产生接口幂等性问题 在计算机应用中,可能遇到网络抖动,临时故障,或者服务调用失败,尤其是分布式系统中,接口调用…

什么是接口的幂等性以及如何实现接口幂等性

目录 1、接口调用存在的问题 2、什么是接口的幂等性 3、不做接口的幂等性会产生什么影响 4、什么情况下需要保证接口的幂等性 4.1 select:查询操作 4.2 insert:新增操作 4.3 delete:删除操作 4.3.1 绝对删除 具有幂等性 4.3.2 相对删…

Python安装pygame教程

Python安装pygame教程 1、版本说明 由于python3.8与pygame存在不兼容的问题,因此在下载Python的时候,需要下载python3.8以下版本的,我下载的是python3.7.5 2、具体步骤 1、在本机控制台通过命令安装pygame pip install pygame 出现错误 …

Pygame 教程(2):重要的概念及对象

本章,将介绍 Pygame 中的重要概念及对象。 导航 上一章:创建第一个应用程序 下一章:绘制图形 文章目录 导航像素坐标Rect 对象Color 对象Surface 对象实例:矩形生成器创建初始窗口创建生成矩形的函数调用函数完整代码 结语 像素…

Pygame教程系列二:MoviePy视频播放篇

【前言】 在pygame 2.0.0版本之前,播放视频可以使用pygame.movie.Movie(xxxx.mpg)播放(只支持.mpg格式的视频),但是在pygame 2.0.0之后,作者因为觉得视频模块维护成本太高就给抛弃了,假如你使用pygame 2.0.0,还调用上述…

Pygame 教程(4):图像传输和绘制文本

本章,你将学会如何传输图像和绘制文本。 导航 上一章:绘制图形 下一章:监测游戏时间 文章目录 导航加载图像导出图像绘制文本实例:画板添加常量限制坐标定义属性绘制色板更改线条粗细处理鼠标事件处理键盘事件调用事件处理方法完…

Pygame基础教程(一)

写在前面的话: 本系列教程仅有一些在本机调试通过的代码(如代码中发现bug,恳请包涵)。除代码中出现的一些主要注释外,不会出现太多其他文字解释,但是,文章中会给出主要模块的官方文档地址。再次…

Pygame 教程(5):监测游戏时间

本章,你将学习如何监测游戏时间。 导航 上一章:图像传输和绘制文本 下一章:努力更新中…… 文章目录 导航监测时间游戏帧速率实例:绘图性能对比结语 监测时间 在游戏程序中,时常需要随着时间的流逝而做出不同的动作…

Pygame教程系列四:播放音频篇

【前言】 pygame播放音频文件这部分相对来说比较简单,主要是用到pygame.mixer模块,不过也有一些地方需要注意的,咱们直接先看看案例 1、案例效果图 2、案例代码 import pygame from mutagen.mp3 import MP3 # 标识是否退出循环 exitFlag Fa…

python3.8安装pygame_Python3.8安装Pygame教程

注:因为最近想用一下Python做一些简单小游戏的开发作为项目练手之用,而Pygame模块里面提供了大量的有用的方法和属性。今天我们就在之前安装过PyCharm的基础上,安装Pygame,下面是安装的步骤,希望能够帮到大家。 第一步…