如何利用Redis Lua脚本防止接口幂等性冲突_实现原子化的Token检查与设置

酷辰大大_3844

酷辰大大_3844

2026-07-09

408人浏览

原创

必须用lua脚本原子执行“判断+删除”,因get与del分离会导致高并发下两次校验都通过而重复提交;脚本需用redis.call("get")判空后del并return 1/0,java调用须用defaultredisscript封装且resulttype设为long.class。

如何利用redis lua脚本防止接口幂等性冲突_实现原子化的token检查与设置

直接用 GET + DEL 两步校验 Token,高并发下必然失效;必须用 Lua 脚本把「存在判断 + 删除」锁死在一次 Redis 执行中,否则重复提交拦不住。

为什么不能分开执行 GET 和 DEL

两个并发请求同时执行 GET token:abc,都拿到非空结果;接着都执行 DEL token:abc,再都走业务逻辑——订单就生成了两次。这不是理论风险,是线上真实会炸的场景。

  • GET 和 DEL 是两个独立命令,Redis 不保证它们之间不被其他客户端插入操作
  • 哪怕加了 EXPIRE,也不能解决中间窗口期的竞态
  • 用 EXISTS 判断后再 DEL 同样无效,本质一样是非原子

Lua 脚本必须这样写:用 redis.call("get") + del 原子返回

核心是让整个判断和清理只返回一个确定值(1 或 0),业务层靠这个值决定是否放行。下面这个脚本是经过生产验证的最小可靠形态:

Redis Skill - 高性能缓存管理
Redis Skill - 高性能缓存管理

Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。

下载
if redis.call("get", KEYS[1]) ~= false then
  redis.call("del", KEYS[1])
  return 1
else
  return 0
end
  • 必须用 redis.call("get", ...),不能用 redis.call("exists", ...),因为 exists 返回整数,而 get 返回 nil 或字符串,Lua 中 ~= false 才能准确捕获 key 不存在的情况
  • 脚本里不能出现 TIME、RANDOMKEY 等非确定性命令,否则 Redis 主从同步会拒绝加载
  • 不要在脚本里做业务逻辑(比如拼接日志、调用外部接口),它只负责“钥匙是否有效+立刻作废”这件事

SpringBoot 中调用 EVAL 的关键配置点

Java 层不是简单拼字符串发过去,得用 DefaultRedisScript 封装并指定返回类型,否则返回值解析会出错:

  • redisScript.setScriptText(...) 必须赋值为上面那段 Lua 字符串
  • redisScript.setResultType(Long.class) —— 因为脚本最后 return 1 是数字,不是字符串,不设类型会导致 ClassCastException
  • 调用时传参: redisTemplate.execute(redisScript, Collections.singletonList(token)),KEYS[1] 对应的就是这个 token 字符串
  • 返回值为 1L 表示校验通过且已删除;0L 表示 token 不存在或已被用过

Token 存储本身也要防覆盖,用 SET NX EX 更稳妥

生成 Token 时别用 set(token, "1"),而是用原子命令确保不会意外覆盖:

  • 推荐写法:stringRedisTemplate.opsForValue().set(token, "1", 5, TimeUnit.MINUTES, RedisSetOption.UPSET)
  • 更底层可直接用:redis.call("set", KEYS[1], ARGV[1], "EX", ARGV[2], "NX"),其中 "NX" 是关键,避免并发生成时写入冲突
  • 过期时间别设太长(比如 24 小时),5~10 分钟足够覆盖用户操作窗口,过长会堆积无效 key

真正容易被忽略的是:Lua 脚本的返回值类型和 Java 调用时的泛型必须严格一致,差一个 Long 和 Integer 就会静默失败;还有就是前端拿 token 后没及时带出,或者后端没校验 header/param 是否为空,这些地方一漏,原子性再强也白搭。

相关专题

更多
登录token无效
登录token无效

登录token无效解决方法:1、检查token的有效期限,如果token已经过期,需要重新获取一个新的token;2、检查token的签名,如果签名不正确,需要重新获取一个新的token;3、检查密钥的正确性,如果密钥不正确,需要重新获取一个新的token;4、使用HTTPS协议传输token,建议使用HTTPS协议进行传输 ;5、使用双因素认证,双因素认证可以提高账户的安全性。

2023.09.14

12446

6

登录token无效怎么办
登录token无效怎么办

登录token无效的解决办法有检查Token是否过期、检查Token是否正确、检查Token是否被篡改、检查Token是否与用户匹配、清除缓存或Cookie、检查网络连接和服务器状态、重新登录或请求新的Token、联系技术支持或开发人员等。本专题为大家提供token相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.14

1505

5

token怎么获取
token怎么获取

获取token值的方法:1、小程序调用“wx.login()”获取 临时登录凭证code,并回传到开发者服务器;2、开发者服务器以code换取,用户唯一标识openid和会话密钥“session_key”。想了解更详细的内容,可以阅读本专题下面的文章。

2023.12.21

3337

3

token什么意思
token什么意思

token是一种用于表示用户权限、记录交易信息、支付虚拟货币的数字货币。可以用来在特定的网络上进行交易,用来购买或出售特定的虚拟货币,也可以用来支付特定的服务费用。想了解更多token什么意思的相关内容可以访问本专题下面的文章。

2024.03.01

5501

5

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

2023.10.19

3068

3

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

2025.10.17

4429

12

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2025.12.29

3709

9

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

2026.01.19

386

15

常用的数据库软件
常用的数据库软件

常用的数据库软件有MySQL、Oracle、SQL Server、PostgreSQL、MongoDB、Redis、Cassandra、Hadoop、Spark和Amazon DynamoDB。更多关于数据库软件的内容详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.02

4229

19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL vs MySQL
PostgreSQL vs MySQL

共1课时 | 178人学习

phpEnv手册
phpEnv手册

共0课时 | 0人学习