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

article/2025/10/3 17:44:34

微信搜索《Java鱼仔》,每天一个知识点不错过

每天一个知识点

什么是接口的幂等性,如何实现接口幂等性?

(一)幂等性概念

幂等性原本是数学上的概念,用在接口上就可以理解为:同一个接口,多次发出同一个请求,必须保证操作只执行一次。
调用接口发生异常并且重复尝试时,总是会造成系统所无法承受的损失,所以必须阻止这种现象的发生。
比如下面这些情况,如果没有实现接口幂等性会有很严重的后果:
支付接口,重复支付会导致多次扣钱
订单接口,同一个订单可能会多次创建。

(二)幂等性的解决方案

唯一索引
使用唯一索引可以避免脏数据的添加,当插入重复数据时数据库会抛异常,保证了数据的唯一性。

乐观锁
这里的乐观锁指的是用乐观锁的原理去实现,为数据字段增加一个version字段,当数据需要更新时,先去数据库里获取此时的version版本号

select version from tablename where xxx

更新数据时首先和版本号作对比,如果不相等说明已经有其他的请求去更新数据了,提示更新失败。

update tablename set count=count+1,version=version+1 where version=#{version}

悲观锁
乐观锁可以实现的往往用悲观锁也能实现,在获取数据时进行加锁,当同时有多个重复请求时其他请求都无法进行操作

分布式锁
幂等的本质是分布式锁的问题,分布式锁正常可以通过redis或zookeeper实现;在分布式环境下,锁定全局唯一资源,使请求串行化,实际表现为互斥锁,防止重复,解决幂等。

token机制
token机制的核心思想是为每一次操作生成一个唯一性的凭证,也就是token。一个token在操作的每一个阶段只有一次执行权,一旦执行成功则保存执行结果。对重复的请求,返回同一个结果。token机制的应用十分广泛。

(三)token机制的实现

这里展示通过token机制实现接口幂等性的案例:github文末自取
首先引入需要的依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.4</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

3.1、配置请求的方法体和枚举类

首先配置一下通用的请求返回体

public class Response {private int status;private String msg;private Object data;//省略get、set、toString、无参有参构造方法
}

以及返回code

public enum ResponseCode {// 通用模块 1xxxxILLEGAL_ARGUMENT(10000, "参数不合法"),REPETITIVE_OPERATION(10001, "请勿重复操作"),;ResponseCode(Integer code, String msg) {this.code = code;this.msg = msg;}private Integer code;private String msg;public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}
}

3.2 自定义异常以及配置全局异常类

public class ServiceException extends RuntimeException{private String code;private String msg;//省略get、set、toString以及构造方法
}

配置全局异常捕获器

@ControllerAdvice
public class MyControllerAdvice {@ResponseBody@ExceptionHandler(ServiceException.class)public Response serviceExceptionHandler(ServiceException exception){Response response=new Response(Integer.valueOf(exception.getCode()),exception.getMsg(),null);return response;}
}

3.3 编写创建Token和验证Token的接口以及实现类

@Service
public interface TokenService {public Response createToken();public Response checkToken(HttpServletRequest request);
}

具体实现类,核心的业务逻辑都写在注释中了

@Service
public class TokenServiceImpl implements TokenService {@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic Response createToken() {//生成uuid当作tokenString token = UUID.randomUUID().toString().replaceAll("-","");//将生成的token存入redis中redisTemplate.opsForValue().set(token,token);//返回正确的结果信息Response response=new Response(0,token.toString(),null);return response;}@Overridepublic Response checkToken(HttpServletRequest request) {//从请求头中获取tokenString token=request.getHeader("token");if (StringUtils.isBlank(token)){//如果请求头token为空就从参数中获取token=request.getParameter("token");//如果都为空抛出参数异常的错误if (StringUtils.isBlank(token)){throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getCode().toString(),ResponseCode.ILLEGAL_ARGUMENT.getMsg());}}//如果redis中不包含该token,说明token已经被删除了,抛出请求重复异常if (!redisTemplate.hasKey(token)){throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getCode().toString(),ResponseCode.REPETITIVE_OPERATION.getMsg());}//删除tokenBoolean del=redisTemplate.delete(token);//如果删除不成功(已经被其他请求删除),抛出请求重复异常if (!del){throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getCode().toString(),ResponseCode.REPETITIVE_OPERATION.getMsg());}return new Response(0,"校验成功",null);}
}

3.4 配置自定义注解

这是比较重要的一步,通过自定义注解在需要实现接口幂等性的方法上添加此注解,实现token验证

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
}

接口拦截器

public class ApiIdempotentInterceptor implements HandlerInterceptor {@Autowiredprivate TokenService tokenService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod= (HandlerMethod) handler;Method method=handlerMethod.getMethod();ApiIdempotent methodAnnotation=method.getAnnotation(ApiIdempotent.class);if (methodAnnotation != null){// 校验通过放行,校验不通过全局异常捕获后输出返回结果tokenService.checkToken(request);}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

3.5 配置拦截器以及redis

配置webConfig,添加拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(apiIdempotentInterceptor());}@Beanpublic ApiIdempotentInterceptor apiIdempotentInterceptor() {return new ApiIdempotentInterceptor();}
}

配置redis,使得中文可以正常传输

@Configuration
public class RedisConfig {//自定义的redistemplate@Bean(name = "redisTemplate")public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){//创建一个RedisTemplate对象,为了方便返回key为string,value为ObjectRedisTemplate<String,Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);//设置json序列化配置Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=newJackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper=new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);//string的序列化StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();//key采用string的序列化方式template.setKeySerializer(stringRedisSerializer);//value采用jackson的序列化方式template.setValueSerializer(jackson2JsonRedisSerializer);//hashkey采用string的序列化方式template.setHashKeySerializer(stringRedisSerializer);//hashvalue采用jackson的序列化方式template.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}
}

最后是controller

@RestController
@RequestMapping("/token")
public class TokenController {@Autowiredprivate TokenService tokenService;@GetMappingpublic Response token(){return tokenService.createToken();}@PostMapping("checktoken")public Response checktoken(HttpServletRequest request){return tokenService.checkToken(request);}
}

其余代码在文末github链接上自取

(四)结果验证

首先通过token接口创建一个token出来,此时redis中也存在了改token

在这里插入图片描述

在jmeter中同时运行50个请求,我们可以观察到,只有第一个请求校验成功,后续的请求均提示请勿重复操作。

在这里插入图片描述

在这里插入图片描述

jmeter压测文件(Token Plan.jmx)和代码自取:github自取


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

相关文章

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

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

接口的幂等性

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

如何保证接口的幂等性?

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

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

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

Python安装pygame教程

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

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

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

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

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

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

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

Pygame基础教程(一)

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

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

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

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

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

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

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

Pygame 教程(3):绘制图形

本章&#xff0c;你将学习如何在 Pygame 中绘制图形。 导航 上一章&#xff1a;重要的概念及对象 下一章&#xff1a;图像传输和绘制文本 文章目录 导航抗锯齿draw 模块实例&#xff1a;跟随鼠标的图形创建初始窗口添加变量捕捉鼠标事件绘制图形完整代码 结语 抗锯齿 抗锯齿…

Mac Pycharm导入Pygame教程(超细)

首先先新建一个想要使用Pygame的项目 进入项目后&#xff0c;点击文件&#xff08;File&#xff09;——新项目设置&#xff08;settings&#xff09; 点击新项目的偏好设置&#xff08;Preferences for new project &#xff09; 随后可以看到 点击Python 编译器&#xff0…

mac python3.8上怎么安装pygame 第三方库_Python3.8安装Pygame教程步骤详解

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

Pygame教程系列一:快速入门篇

【简介】 Pygame 是python用来开发视频游戏的游戏引擎&#xff0c;底层主要是SDL库实现&#xff0c;算是目前利用python开发小游戏的一个性能比较高的一个游戏框架 一、安装pygame 使用pip下载安装 pip install pygame二、入门案例详析 1、示例效果 2、示例代码 import os …

pygame教程3

目录 做个小游戏精灵类精灵类介绍使用精灵类 做个小游戏 这个小游戏使用了pygame教程2的知识。 #↓初始化 import pygame,sys from pygame.locals import * pygame.init()#初始化pygame。 screen pygame.display.set_mode((800,600)) pygame.display.set_caption("Hell…

pygame教程2

目录 响应键盘上的事件让画面动起来了解x轴和y轴画圆形圆形动起来 响应键盘上的事件 import pygame#导入pygame。 import sys#导入sys from pygame.locals import *#导入pygame所有的常量&#xff0c;方便以后使用。 pygame.init()#初始化pygame。 screen pygame.display.set…

pygame教程笔记

pygame教程 安装pygameGame Development 1-1: Getting Started with PygameGame Development 1-2: Working with SpritesGame Development 1-3: More About SpritesPygame Shmup Part 1: Player Sprite and ControlsPygame Shmup Part 2: Enemy SpritesPygame Shmup Part 3: Co…

pygame基础教程

pygame简介 pygame可以实现python游戏的一个基础包。 pygame实现窗口 初始化pygame&#xff0c;init()类似于java类的初始化方法&#xff0c;用于pygame初始化。 pygame.init() 设置屏幕&#xff0c;(500,400)设置屏幕初始大小为500 * 400的大小&#xff0c; 0和32 是比较高…