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

为什么直接用 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 KILL或SHUTDOWN NOSAVE,此时需捕获RedisSystemException并重试 - 如果脚本依赖外部状态(比如先查 DB 再操作 Redis),这个「查 DB」步骤不在原子范围内,仍需靠业务层加锁或版本控制兜底
真正容易被忽略的是:Lua 脚本里调用 redis.call() 失败会直接抛异常终止执行,而 redis.pcall() 会捕获并返回 error table——生产环境建议统一用 pcall 包裹关键调用,再手动判断返回值。










