Redis 集群不支持 Lua 脚本?别被报错骗了,真相没那么简单
单机 Redis 上跑得好好的 Lua 脚本,部署到集群后连续报了两个错——第一个是 Redis 的规矩没守好,第二个报错直接说"集群不支持",差点信了。冷静下来翻了源码才发现,这锅不该 Redis 背。
文章目录
一、背景
最近做了一个基于 Redis Lua 的三方接口健康检查功能(详见:三方接口挂了还在傻傻调?教你用 Redis Lua 实现自动熔断),核心逻辑是:每次调用三方接口后,用 Lua 脚本原子地记录结果、统计成功率、判定是否屏蔽。
Lua 脚本操作了两个 Key:
| Key | 类型 | 用途 |
|---|---|---|
channel_health:{渠道} |
List | 滑动窗口,存储最近 N 次调用结果 |
channel_blocked:{渠道} |
String | 屏蔽标记,存在即被屏蔽,TTL 到期自动恢复 |
脚本在测试环境(单机 Redis)上跑得好好的。然后部署到线上 Redis Cluster——好戏开始了。
二、第一个坑:CROSSSLOT
2.1 报错信息
CROSSSLOT Keys in request don't hash to the same slot
2.2 原因
Redis Cluster 将数据分布在 16384 个 slot 上。Lua 脚本操作多个 Key 时,Redis 要求所有 Key 必须在同一个 slot,否则拒绝执行。
当时的 Key 设计:
String healthKey = "channel_health:" + channelId; // 整个 Key 参与 hash → slot A
String blockKey = "channel_blocked:" + channelId; // 整个 Key 参与 hash → slot B
两个 Key 前缀不同,CRC16 hash 出来的 slot 自然不同,直接 GG。
2.3 解决:Hash Tag
Redis 提供了 Hash Tag 机制:Key 中 {...} 内的部分作为 hash 计算依据。
// 改造后:只有 {channelId} 参与 hash,slot 相同
String healthKey = "channel_health:{" + channelId + "}";
String blockKey = "channel_blocked:{" + channelId + "}";
原理:channel_health:{5} 和 channel_blocked:{5} 都只对 5 做 CRC16 hash,结果一致,必然落在同一个 slot。
Hash Tag 不只是 Lua 脚本需要,
MGET、Pipeline、事务等涉及多 Key 的操作在集群模式下都需要用它来保证 Key 同 slot。
加上 Hash Tag,满怀信心地重新部署——
三、第二个坑:EvalSha is not supported
3.1 报错信息
Hash Tag 加好了,再跑,换了个姿势报错:
org.springframework.dao.InvalidDataAccessApiUsageException:
EvalSha is not supported in cluster environment.
at o.s.d.r.connection.jedis.JedisClusterScriptingCommands.evalSha(...)
at o.s.d.r.connection.DefaultedRedisConnection.evalSha(...)
等等,Redis Cluster 不支持 Lua?我刚才白折腾了?
3.2 原因
冷静下来看了一下源码,真相是:
这不是 Redis 的问题,是 Spring Data Redis 的问题。 Redis Cluster 完全支持 Lua 脚本(前提是所有 Key 在同一个 slot)。
问题出在 Spring Data Redis(2.x)+ Jedis 这个组合上。JedisClusterScriptingCommands.evalSha() 直接写死了抛异常:
// Spring Data Redis 源码
public <T> T evalSha(byte[] scriptSha, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
throw new InvalidDataAccessApiUsageException(
"EvalSha is not supported in cluster environment.");
}
而 DefaultRedisScript 默认走 evalSha(先缓存脚本 SHA1,用 SHA1 执行),在集群模式下直接被拦截。
你费劲考了驾照,结果发现车上了锁,钥匙在另一个人手里。
3.3 解决:换用 Redisson
既然 Spring Data Redis + Jedis 走不通,换一个支持集群 Lua 的客户端——Redisson:
String healthKey = "channel_health:{" + channelId + "}";
String blockKey = "channel_blocked:{" + channelId + "}";
List<Object> result = redissonClient.getScript(new StringCodec()).eval(
RScript.Mode.READ_WRITE,
LUA_SCRIPT,
RScript.ReturnType.MULTI,
Arrays.asList(healthKey, blockKey),
success ? "1" : "0",
String.valueOf(windowSize),
String.valueOf(successRate),
String.valueOf(blockMinutes * 60),
String.valueOf((blockMinutes + 30) * 60));
部署,执行,终于成功了。
3.4 其他可选方案
| 方案 | 说明 | 推荐度 |
|---|---|---|
| Redisson | 原生支持集群 Lua,API 简洁 | 推荐 |
| Lettuce | Spring Data Redis 换 Lettuce 驱动,支持集群 evalsha | 推荐 |
| 升级 Spring Boot | Spring Data Redis 3.x 已修复此问题 | 视项目情况 |
| 手动调用 eval | 绕过 evalSha,直接用 eval 发送完整脚本 | 能用但不优雅 |
四、总结
4.1 踩坑清单
| 关卡 | 报错 | 根因 | 解决方案 |
|---|---|---|---|
| 第一坑 | CROSSSLOT Keys in request don't hash to the same slot |
多 Key 不在同一个 slot | 使用 Hash Tag {...} |
| 第二坑 | EvalSha is not supported in cluster environment |
Spring Data Redis 2.x + Jedis 写死了 throw | 换用 Redisson 或 Lettuce |
4.2 经验
- Redis Cluster 支持 Lua 脚本,别被报错信息误导——那是客户端框架的锅
- Hash Tag 是集群多 Key 操作的基础,不只是 Lua,
MGET、Pipeline、事务都需要 - Spring Data Redis + Jedis 在集群模式下有功能缺失,大量使用 Lua 建议直接上 Redisson 或切 Lettuce
- 单机 → 集群不是简单加节点,代码层面也要适配,上线前务必在集群环境跑一遍
如果你也在 Redis Cluster 上跑 Lua 脚本,收藏这篇,能帮你省掉不少排坑时间 🍻
更多推荐




所有评论(0)