如何在Spring Boot中使用Redis执行Lua脚本保证原子性

小敏大大_5656

小敏大大_5656

2026-09-24

858人浏览

原创

直接用 redistemplate.execute() 容易出错,因脚本中 keys/argv 使用不规范、defaultredisscript 未复用、返回类型不匹配、集群 key 槽不一致及 lua 异常处理不当等导致 noscript、classcastexception、crossslot 等错误。

如何在spring boot中使用redis执行lua脚本保证原子性

为什么直接用 RedisTemplate.execute() 容易出错

Spring Boot 默认的 RedisTemplate.execute() 方法在传入 Lua 脚本时,如果脚本里用了 KEYS[1] 但没传 key,或传了 key 却没在脚本中引用,会触发 Redis 的「script not found」或「NOSCRIPT」错误;更隐蔽的问题是:当使用默认的 DefaultRedisScript 且未显式设置 setSha1(),Spring 可能反复上传同一脚本,造成额外网络开销和潜在并发竞争。

  • 必须确保 RedisScript 实例是单例复用的(不能每次 new),否则 SHA1 缓存失效
  • 脚本中所有 key 必须通过 KEYS 数组传入,硬编码 key 名会导致 EVAL 拒绝执行(集群模式下尤其严格)
  • execute() 返回值类型由脚本决定,但 Spring 不做自动转换——返回 Long 却用 String.class 接收会抛 ClassCastException

正确声明和复用 DefaultRedisScript

把脚本内容、返回类型、SHA1 值三者绑定成一个不可变对象,放在 @Configuration 类里作为 @Bean 注册,避免重复解析和上传:

@Bean
public RedisScript<long> incrByIfLessThanScript() {
    DefaultRedisScript<long> script = new DefaultRedisScript();
    script.setScriptText("if tonumber(redis.call('GET', KEYS[1])) 
<ul>
<li>脚本中只能用 <code>KEYS[1]</code>、<code>KEYS[2]</code> 引用传入的 key 列表,不能写死 <code>"user:count"</code>
</li>
<li>
<code>setResultType()</code> 必须与 Lua 中 <code>return</code> 类型一致:返回数字用 <code>Long.class</code>,字符串用 <code>String.class</code>,数组用 <code>List.class</code>
</li>
<li>若脚本较长,建议从 classpath 加载(<code>new ClassPathResource("script.lua").getInputStream()</code>),避免字符串拼接难维护</li>
</ul>
<h3>调用时传参顺序和类型必须严格匹配</h3>
<p>Redis 执行 Lua 的参数顺序固定为:<code>script, keys, args...</code>。Spring 的 <code>execute()</code> 方法要求 keys 和 args 都是 <code>List</code>,且顺序不能颠倒:</p><div class="aritcle_card flexRow artxards">
											<div class="artcardd flexRow">
												<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理"><img
														src="https://img.php.cn/upload/skill/000/000/081/178960683454849.jpg" alt="Redis Skill - 高性能缓存管理" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
												<div class="aritcle_card_info flexColumn">
													<a rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理" class="overflowclass">Redis Skill - 高性能缓存管理</a>
													<p class="overflowclass">Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。</p>
												</div>
												<a rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
												</a>
											</div>
										</div>
<pre class="brush:php;toolbar:false;">List<string> keys = Arrays.asList("counter:123");
List<string> args = Arrays.asList("100", "5"); // ARGV[1] = "100", ARGV[2] = "5"
Long result = redisTemplate.execute(
    incrByIfLessThanScript(), 
    keys, 
    args.toArray(new String[0])
);</string></string>
  • keys 列表长度必须 ≥ 脚本中最大 KEYS[n] 下标,否则报 ERR Error running script
  • args 元素类型统一为 String,Redis 自动转数字;不要传 Integer,否则序列化后变成 "java.lang.Integer@abc" 导致脚本解析失败
  • 集群模式下,所有 keys 必须落在同一个 slot,否则报 CROSSSLOT 错误——可通过 {counter:123} 这种带花括号的 key 名强制路由到同一节点

原子性不是万能的:注意 Lua 脚本的边界限制

Redis 的 Lua 原子性仅保证「脚本内所有命令按序执行,不被其他客户端中断」,但无法规避外部因素导致的逻辑漏洞:

  • 脚本内不能调用阻塞命令(如 BLPOP),否则整个 Redis 实例卡住
  • 脚本运行超时(默认 5 秒)会触发 SCRIPT KILLSHUTDOWN NOSAVE,此时需捕获 RedisSystemException 并重试
  • 如果脚本依赖外部状态(比如先查 DB 再操作 Redis),这个「查 DB」步骤不在原子范围内,仍需靠业务层加锁或版本控制兜底

真正容易被忽略的是:Lua 脚本里调用 redis.call() 失败会直接抛异常终止执行,而 redis.pcall() 会捕获并返回 error table——生产环境建议统一用 pcall 包裹关键调用,再手动判断返回值。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

redis spring boot

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

2025.08.06

2131

22

Java Spring Security 与认证授权
Java Spring Security 与认证授权

本专题系统讲解 Java Spring Security 框架在认证与授权中的应用,涵盖用户身份验证、权限控制、JWT与OAuth2实现、跨站请求伪造(CSRF)防护、会话管理与安全漏洞防范。通过实际项目案例,帮助学习者掌握如何 使用 Spring Security 实现高安全性认证与授权机制,提升 Web 应用的安全性与用户数据保护。

2026.01.26

397

25

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

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

2023.11.02

4049

19

内存数据库有哪些
内存数据库有哪些

内存数据库有Redis、Memcached、Apache Ignite、VoltDB、TimesTen、H2 Database、Aerospike、Oracle TimesTen In-Memory Database、SAP HANA和ache Cassandra。更多关于内存数据库相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.14

3555

11

mongodb和redis哪个读取速度快
mongodb和redis哪个读取速度快

redis 的读取速度比 mongodb 更快。原因包括:1. redis 使用简单的键值存储,而 mongodb 存储 json 格式的数据,需要解析和反序列化。2. redis 使用哈希表快速查找数据,而 mongodb 使用 b-tree 索引。因此,redis 在需要高性能读取操作的应用程序中是一个更好的选择。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.02

6292

6

redis怎么做缓存服务器
redis怎么做缓存服务器

redis 作为缓存服务器的答案:redis 是一款开源、高性能、分布式的键值存储,可作为缓存服务器使用。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

603

6

redis怎么解决数据一致性
redis怎么解决数据一致性

redis 提供了两种一致性模型,以维护副本数据一致性:强一致性 (sync) 确保写操作仅在复制到所有从节点后才完成;最终一致性 (async) 则在主节点上写操作后认为已完成,牺牲一致性换取性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

716

6

mysql和redis怎么保证双写一致性
mysql和redis怎么保证双写一致性

确保 mysql 和 redis 双写一致性的技术包括:1、事务性更新:同时更新 mysql 和 redis,保证一致性;2、主从复制:mysql 主服务器更改同步到 redis 从服务器;3、基于事件的更新:mysql 记录更改并发送到 redis等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

5742

6

redis缓存一般存些什么数据
redis缓存一般存些什么数据

redis缓存中存储的数据类型包括:字符串、哈希、列表、集合、有序集合、位图、地理空间数据和hyperloglog。这些数据类型适用于存储各种数据,从简单信息到复杂对象和地理位置。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

1120

6

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
phpEnv手册
phpEnv手册

共0课时 | 0人学习

进程与SOCKET
进程与SOCKET

共6课时 | 0.5万人学习