SpringBoot + Redis + Token 解决接口幂等性问题,挑选最佳方案!

前言

SpringBoot实现接口幂等性的方案有很多,其中最常用的一种就是 token + redis 方式来实现。

下面我就通过一个案例代码,帮大家理解这种实现逻辑。

原理

前端获取服务端getToken() -> 前端发起请求 -> header中带上token -> 服务端校验前端传来的token和redis中的token是否一致 -> 一致则删除token -> 执行业务逻辑

案例

1、利用Token + Redis

核心代码如下:

@RestController
public class IdempotentController {@Autowiredprivate RedisTemplate<String, String> redisTemplate;/*** 提交接口,需要携带有效的token参数*/@PostMapping("/submit")public String submit(@RequestParam("token") String token) {// 检查Token是否有效if (!isValidToken(token)) {return "Invalid token";}// 具体的接口处理逻辑,在这里实现你的业务逻辑// 使用完毕后删除TokendeleteToken(token);return "Success";}/*** 检查Token是否有效*/private boolean isValidToken(String token) {// 检查Token是否存在于Redis中return redisTemplate.hasKey(token);}/*** 删除Token*/private void deleteToken(String token) {// 从Redis中删除TokenredisTemplate.delete(token);}/*** 生成Token接口,用于获取一个唯一的Token*/@GetMapping("/generateToken")public String generateToken() {// 生成唯一的TokenString token = UUID.randomUUID().toString();// 将Token保存到Redis中,并设置过期时间(例如10分钟)redisTemplate.opsForValue().set(token, "true", Duration.ofMinutes(10));return token;}
}

上述代码和前面描述的原理一致,但实际上存在问题,那就是在高并发场景下依然会有幂等性问题,这是因为没有充分利用redis的原子性

2、利用Redis原子性

接下来,使用Redis的原子性操作,比如SETNXEXPIRE来实现更可靠的幂等性控制。

我们优化一下代码,如下:

@RestController
public class IdempotentController {@Autowiredprivate RedisTemplate<String, String> redisTemplate;/*** 提交接口,需要携带有效的token参数*/@PostMapping("/submit")public String submit(@RequestParam("token") String token) {// 使用SETNX命令尝试将Token保存到Redis中,如果返回1表示设置成功,说明是第一次提交;否则返回0,表示重复提交Boolean success = redisTemplate.opsForValue().setIfAbsent(token, "true", Duration.ofMinutes(10));if (success == null || !success) {return "Duplicate submission";}try {// 具体的接口处理逻辑,在这里实现你的业务逻辑return "Success";} finally {// 使用DEL命令删除TokenredisTemplate.delete(token);}}
}

可以看到,我们使用了setIfAbsent方法来尝试将Token保存到Redis中,并设置过期时间(例如10分钟)。如果设置成功,则执行具体的接口处理逻辑,处理完成后会自动删除Token。如果设置失败,说明该Token已存在,即重复提交,直接返回错误信息。

注意,上述代码中删除Token的操作在finally块中执行,无论接口处理逻辑成功与否都会确保删除Token,以免出现异常导致未能正确删除Token的情况。

通过使用Redis的原子性操作,我们可以更可靠地实现接口的幂等性,并在高并发情况下提供更好的性能和准确性。

但是,在高并发场景下,这样其实依然有问题,依然有概率出现幂等性问题。

这是因为,高并发场景下,可能会出现同时两个请求都从redis中获取到token,在服务端都能校验成功,最终破坏幂等性。

所以,还有优化的空间。

3、结合Lua脚本

可以使用Lua脚本配合Redis的原子性操作来实现更可靠的幂等性控制。

优化后的完整代码如下:

@RestController
public class IdempotentController {@Autowiredprivate RedisTemplate<String, String> redisTemplate;/*** 提交接口,需要携带有效的token参数*/@PostMapping("/submit")public String submit(@RequestHeader("token") String token) {if (StringUtils.isBlank(token)) {return "Missing token";}DefaultRedisScript<Boolean> script = new DefaultRedisScript<>(LUA_SCRIPT, Boolean.class);// 使用Lua脚本执行原子性操作Boolean success = redisTemplate.execute(script, Collections.singletonList(token), "true", "600");if (success == null || !success) {return "Duplicate submission";}try {// 具体的接口处理逻辑,在这里实现你的业务逻辑return "Success";} finally {// 使用DEL命令删除TokenredisTemplate.delete(token);}}/*** 生成Token接口,用于获取一个唯一的Token*/@GetMapping("/generateToken")public String generateToken() {// 生成唯一的TokenString token = UUID.randomUUID().toString();// 将Token保存到Redis中,并设置过期时间(例如10分钟)redisTemplate.opsForValue().set(token, "true", Duration.ofMinutes(10));return token;}// Lua脚本private final String LUA_SCRIPT = "if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then\n" +"    redis.call('EXPIRE', KEYS[1], ARGV[2])\n" +"    return true\n" +"else\n" +"    return false\n" +"end";
}

其中,这段Lua脚本的含义如下:

  1. 首先定义了一个私有 final 字符串变量 LUA_SCRIPT,用于存储Lua脚本的内容。

  2. 在Lua脚本中使用了Redis的命令,以及参数引用。下面是逐行解释:

  • if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then:使用 Redis 的 SETNX 命令,在键 KEYS[1] 中设置值为 ARGV[1](ARGV 是一个参数数组)。如果 SETNX 返回值为 1(表示设置成功),则执行以下代码块。

  • redis.call('EXPIRE', KEYS[1], ARGV[2]):使用 Redis 的 EXPIRE 命令,在键 KEYS[1] 设置过期时间为 ARGV[2] 秒。

  • return true:返回布尔值 true 给调用方,表示设置和过期时间设置都成功。

  • else:如果 SETNX 返回值不为 1,则执行以下代码块。

  • return false:返回布尔值 false 给调用方,表示设置失败。

所以,这段Lua脚本的目的是在 Redis 中设置一个键值对,并为该键设置过期时间。如果键已存在,脚本将返回 false 表示设置失败;如果键不存在,脚本将返回 true 表示设置和过期时间设置都成功。

总结

在处理接口幂等性的问题中,token机制使用最广泛,也是性能比较好的方案。

其实,还有一种比较简单的方案,就是使用Redission分布式锁。

这种方案的编码非常少,效果也能达到,但上锁必有损耗,所以综合性能是不如本文方案的,但因为封装的好,编码简单,也是企业中很受欢迎的方式。

我的过往文章中有关于Redisson配合自定义注解实现防重的文章,有兴趣的可以去看一下。

Redisson虽然实现简单,但本身不利于学习,在学习阶段,我不推荐直接上手Redisson。

好了,今天的知识学会了吗?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/80106.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云原生之使用Docker部署Nas-Cab个人NAS平台

云原生之使用Docker部署Nas-Cab个人NAS平台 一、Nas-Cab介绍二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载Nas-Cab镜像五、部署Nas-Cab5.1 创建挂载目录5.2 创建Nas-Cab容…

利用idea新创建maven项目时的一些基本配置

1.修改项目默认的maven仓库 file->Settings->Build 2.设置项目的jdk版本 设置完点OK即可。 同样的我们还需要在项目配置中进行修改。 通过以上设置一般就可以解决jdk版本不兼容地方问题。

exgcd, 线性同余方程

878. 线性同余方程 - AcWing题库 878. 线性同余方程 给定 n 组数据 ai,bi,mi&#xff0c;对于每组数求出一个 xi&#xff0c;使其满足 aixi≡bi(modmi)&#xff0c;如果无解则输出 impossible。 输入格式&#xff0c; 第一行包含整数 n。 接下来 n 行&#xff0c;每行包含…

网络安全深入学习第一课——热门框架漏洞(RCE-代码执行)

文章目录 一、代码执行概述二、代码执行相关函数1、eval2、assert3、${ }执行代码 三、命令执行和代码执行的区别 一、代码执行概述 代码执行定义&#xff1a; ------ 应用程序在调用一些能够将字符串转换为代码的函数&#xff08;如PHP中的eval&#xff09;时&#xff0c;没有…

稀土系储氢合金 压力-组成等温线 PCI 的测试方法

声明 本文是学习GB-T 29918-2023 稀土系储氢合金 压力-组成等温线 PCI 的测试方法. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 5 方法2:电化学法 5.1 方法提要 以储氢合金作负极&#xff0c;烧结氢氧化亚镍作正极&#xff0c;氢氧化钾水溶液作电…

postgresql-视图

postgresql-视图 视图概述使用视图的好处 创建视图修改视图删除视图递归视图可更新视图WITH CHECK OPTION 视图概述 视图&#xff08;View&#xff09;本质上是一个存储在数据库中的查询语句。视图本身不包含数据&#xff0c;也被称为 虚拟表。我们在创建视图时给它指定了一个…

数字IC设计之时序分析基础概念汇总

1 时钟Clock 理想的时钟模型是一个占空比为50%且周期固定的方波。时钟是FPGA中同步电路逻辑运行的一个基准。理想的时钟信号如下图: 2 时钟抖动Clock Jitter 理想的时钟信号是完美的方波&#xff0c;但是实际的方波是存在一些时钟抖动的。那么什么是时钟抖动呢?时钟抖动&#…

17-垃圾回收相关概念

目录 一、System.gc()的理解二、内存溢出和内存泄漏2、内存泄漏 三、Stop the World1、什么是 stop the word ? 四、垃圾回收的并行和并发1、并发和并发2、垃圾回收的并行和并发 五、安全点与安全区域1、什么是安全点&#xff1f;2、安全区域 六、强引用&#xff08;不可回收&…

苹果电脑Mac系统运行速度又卡又慢是怎么回事?

通常大家处理Mac运行速度慢的方法不是重启就是清空废纸篓&#xff0c;但是这两种方法对于Mac提速性能的效果是微之甚微的&#xff0c;想要彻底解决Mac运行速度慢&#xff0c;你应该试试一下三种方法~ 1、清理磁盘空间 硬盘空间过少是Mac运行变慢很大的一个因素&#xff0c;各…

华纳云:如何进行Linux CPU中的Kernel space分析

分析 Linux CPU 中的 Kernel Space&#xff08;内核空间&#xff09;通常需要使用一些工具和技术&#xff0c;以便了解内核活动和性能问题。以下是一些方法和工具&#xff0c;可以帮助您在 Linux 系统上进行 Kernel Space 分析&#xff1a; dmesg 命令&#xff1a; dmesg 命令…

解决MySQL 8.0以上版本设置大小写不敏感的问题

MySQL 8.0以上版本默认区分大小写&#xff0c;但在低版本&#xff08;如5.7&#xff09;中&#xff0c;可以通过在my.cnf配置文件的[mysqld]节下添加lower_case_table_names1来设置大小写不敏感。然而&#xff0c;在MySQL 8.0以上版本中&#xff0c;添加此配置可能导致MySQL服务…

【LeetCode刷题笔记】动态规划 — 70.爬楼梯

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多算法知识专栏&#xff1a;算法分析&#x1f525; 给大家跳段街舞感谢…

Unity中 UI Shader的基本功能

文章目录 前言一、实现思路1、暴露一个 2D 类型的属性来接受UI的纹理2、设置shader的层级为TransParent半透明渲染层级&#xff0c;一般UI都是在这个渲染层级3、更改混合模式&#xff0c;是 UI 使用的纹理&#xff0c;该透明的地方透明 二、代码实现 前言 Unity中 UI Shader的…

Python爬虫逆向猿人学刷题系列——第七题

题目&#xff1a;采集这5页中胜点列的数据&#xff0c;找出胜点最高的召唤师&#xff0c;将召唤师姓名填入答案中 地址&#xff1a;https://match.yuanrenxue.cn/match/7 本题主要是考察字体的动态变化&#xff0c;同样也是从字体文件下手构造出映射关系就好&#xff0c;但本题…

一个Linux主机巡检脚本

功能&#xff1a; 对Linux主机的物理资源消耗进行监测和统计&#xff0c;对运行在本机上的服务进程进行监测和统计&#xff0c;列出本机上的账户有效期 使用场景&#xff1a; 在没有 Promethes、zabbix、夜莺等监控工具的条件下&#xff0c;通过 crontab 排程应用本程序完成对主…

【Training versus Testing】Positive intervals

GPT的解释&#xff1a; 这段话解释了在一维中&#xff0c;针对 "Positive intervals"&#xff08;正间隔&#xff09;假设类别 H 的成长函数 mq(N) 的计算方式&#xff0c;以及如何得出这个成长函数。 1. "Positive intervals" 指的是一维情境中的假设&a…

Maven导入和引用本地包的方法

maven项目中使用到了TrendDB_API.jar包&#xff0c;项目是直接导入的jar包而没用用maven的形式导入&#xff0c;导致在idea编辑时无法识别本地jar包&#xff0c;最后我采用的是用maven方式导入下本地jar包。 <dependency><groupId>TrendDB_API</groupId><…

clickhouse在执行alter table update delete等命令后数据没有更新

clickhouse之删除数据或更新数据无效的解决思路 例如&#xff1a; ALTER TABLE 表名 DELETE WHERE 条件 ALTER TABLE 表名 UPDATE column1 expr1 [, ...] WHERE filter_expr分析原因&#xff1a; 我们都知道ClickHouse内核中的MergeTree存储一旦生成一个Data Part&#xff0…

推荐书目:Python从入门到精通(文末送书)

目录 编辑推荐 内容简介 作者简介 前言/序言 本书特点 编辑推荐 “软件开发视频大讲堂”丛书是清华社计算机专业基础类零售图书畅销品牌之一。 &#xff08;1&#xff09;2008—2023年&#xff0c;丛书累计修订7次&#xff0c;销售400万册&#xff0c;深受广大程序员喜爱。…

Spring安全配置: 构建安全稳固的Java应用

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…