Jedis不支持Lua脚本自动重试和连接自动归还,需手动封装重试逻辑并确保每次eval后通过try-with-resources或finally归还连接;失败原因包括连接重置、脚本超时、认证失败及集群重定向等。

直接结论:Jedis 本身不提供 Lua 脚本自动重试,也不自动管理连接归还;重试逻辑必须手动封装,连接池复用依赖严格调用 jedis.close() 或 pool.returnResource()(旧版) / try-with-resources(新版)。
为什么 eval 失败后不能靠 Jedis 自动重试
Jedis 的 eval 方法是纯命令转发,底层调用 Protocol.sendCommand 后直接解析响应。它不会捕获 JedisConnectionException、JedisDataException 或超时异常并重试——哪怕你传的是 SHA1 哈希值、脚本已预加载,只要连接断了或 Redis 返回 NOAUTH/BUSY,就原样抛出异常。
常见触发点包括:
- Redis 主从切换瞬间,连接被服务端主动关闭,
eval抛JedisConnectionException: java.net.SocketException: Connection reset - 脚本执行超时(
lua-time-limit配置),返回ERR BUSY Redis is busy running a script - 集群模式下 key 不在当前节点,报
MOVED或ASK错误(此时UnifiedJedis会重定向,但原始Jedis不会)
手动实现带重试的 Lua 执行(含连接池安全归还)
核心原则:每次 eval 必须绑定一个明确生命周期的 Jedis 实例,且无论成功失败,都必须确保连接归还到池中。推荐用 try-with-resources(Jedis ≥ 3.0)或显式 finally 块。
示例代码(使用 JedisPool + 指数退避重试):
public Object evalWithRetry(JedisPool pool, String script, List<String> keys, List<String> args, int maxRetries) {
Jedis jedis = null;
for (int i = 0; i <= maxRetries; i++) {
jedis = pool.getResource();
try {
return jedis.eval(script, keys, args);
} catch (JedisConnectionException | JedisDataException e) {
if (i == maxRetries) throw e;
// 简单退避:100ms → 200ms → 400ms
try { Thread.sleep((long) Math.pow(2, i) * 100); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
} finally {
if (jedis != null) jedis.close(); // 注意:close() 在新版 Jedis 中等价于 returnResource()
}
}
return null;
}关键点:
-
jedis.close()是安全的,新版 Jedis(≥ 3.0)中该方法内部判断是否为池化连接,是则归还,否则才真正关闭 Socket - 不要在
catch块里直接return,否则连接永远不归还,池迅速耗尽 - 重试次数建议 ≤ 3,避免雪崩;
BUSY类错误可考虑先SCRIPT KILL再重试
testOnBorrow 和 testWhileIdle 对 Lua 脚本没用
这两个配置项只影响连接借出/空闲时的 PING 探活,而 eval 执行过程中的连接中断无法被提前感知。开启 testOnBorrow=true 反而增加每次脚本执行前的额外 RTT 开销,尤其在高并发短脚本场景下得不偿失。
更务实的做法:
- 禁用
testOnBorrow(保持默认false) - 启用
testWhileIdle=true+timeBetweenEvictionRunsMillis=30000,让空闲连接定期自检,清理掉被 Redistimeout断开的“死连接” - 配合监控
pool.getNumActive()和pool.getNumIdle(),发现活跃连接长期满、空闲连接持续为 0,大概率是连接泄漏或未归还
容易忽略的坑:Lua 脚本里的 KEYS 和 ARGV 顺序与 Java 传参强绑定
jedis.eval(script, keys, args) 中的 keys 列表长度必须等于脚本中 KEYS[1]、KEYS[2] … 引用的个数,否则 Redis 报 ERR Error running script (call to f_...): @user_script:1: ERR wrong number of arguments。这个错误不是连接问题,重试无效,必须检查脚本和 Java 调用是否一致。
典型翻车场景:
- 脚本里写
redis.call('GET', KEYS[1]),但 Java 传了Arrays.asList("k1", "k2")—— 多传一个 key,直接报错 - 脚本用
ARGV[1],但 Java 的args是null或空列表 —— Redis 认为ARGV[1]不存在,可能返回nil导致后续逻辑空指针 - 集群模式下,所有
KEYS必须落在同一个 slot,否则报CROSSSLOT,这个错误也无法靠重试绕过
最稳妥的方式:把脚本逻辑和参数契约写进接口文档,用单元测试覆盖所有 keys/args 组合,而不是指望运行时重试兜底。

















