Redis Lua 脚本异常:“NOSCRIPT No matching script” 故障分析与修复
一、事件背景
项目使用 Spring Data Redis + Redisson 执行 Lua 脚本,实现分布式原子更新逻辑。
示例代码:
1 | DefaultRedisScript<List> redisScript = new DefaultRedisScript<>(); |
Redis 为单机模式部署。
镜像重构后应用启动正常,但执行 Lua 脚本时报错。
二、问题现象
报错堆栈
1 | Caused by: org.redisson.client.RedisException: NOSCRIPT No matching script. Please use EVAL.. |
含义:Redis 未找到匹配的脚本 SHA1 缓存,因此 Redisson 无法执行 EVALSHA。
三、初步分析
Redis 的 Lua 执行过程:
- 客户端首次使用
EVAL发送脚本; - Redis 编译脚本,缓存其内容与 SHA1;
- 后续客户端使用
EVALSHA直接执行; - 若 SHA1 不存在,返回
NOSCRIPT。
因此,本次报错说明:
Redis 并未加载到客户端所认为的“同一份脚本内容”。
四、排查过程
1️⃣ 验证 Redis 是否缓存脚本
1 | redis-cli SCRIPT EXISTS f21f6c8c24daabf... |
返回:
1 | 1) (integer) 0 |
→ Redis 未缓存该 SHA1。
2️⃣ 检查 Lua 脚本内容
cas.lua 文件中含中文注释:
1 | -- 校验版本号 |
删除中文注释后再执行:
1 | redis-cli --eval scripts/cas.lua |
✅ Redis 能正常执行。
→ 表面上似乎“中文注释导致 Redis 编译失败”,但实际上:
Redis 支持 UTF-8 中文脚本,真正问题是客户端加载时字符编码被破坏,发送给 Redis 的内容与原文件不一致。
3️⃣ 检查容器环境编码
进入容器:
1 | locale |
输出:
1 | locale: Cannot set LC_CTYPE... |
虽然声明了 UTF-8,但系统未安装 locale 数据。
结果:
- JVM 默认编码回退为 ASCII (
ANSI_X3.4-1968); ResourceScriptSource按默认编码读取脚本;- 中文注释被错误解析为乱码;
- Redis 收到的字节序列与原文件不同;
- → SHA1 不匹配;
- → Redis 报
NOSCRIPT。
验证:
1 | java -XshowSettings:properties -version | grep file.encoding |
输出:
1 | file.encoding = ANSI_X3.4-1968 |
证实了 JVM 默认编码非 UTF-8。
4️⃣ 验证 Redisson 脚本加载机制
Spring 的 ResourceScriptSource 使用系统默认字符集读取脚本文件:
1 | Charset.defaultCharset() // => 取决于 JVM file.encoding |
若环境未启用 UTF-8,脚本在加载时被破坏。
此时客户端计算的 SHA 与 Redis 实际接收内容的 SHA 不同。
Redis 自然找不到该脚本 → 报 NOSCRIPT。
五、根因分析 (RCA)
| 分类 | 描述 |
|---|---|
| 问题类型 | 环境编码不一致 |
| 触发条件 | Lua 脚本中包含中文字符 |
| 直接原因 | JVM 读取脚本时使用非 UTF-8 编码导致内容损坏 |
| 最终结果 | Redis 与客户端 SHA1 不一致,报 NOSCRIPT |
六、解决方案
✅ 快速修复方案(环境级)
在 Dockerfile 中正确配置 UTF-8 locale:
1 | RUN apt-get update && \ |
验证:
1 | locale |
🧩 稳健方案(代码级)
避免依赖系统编码,手动读取脚本并指定 UTF-8:
1 | String script = Files.readString( |
此方法绕过 ResourceScriptSource,完全杜绝编码不一致问题。
七、经验总结
| 项目 | 内容 |
|---|---|
| 故障类型 | Redis Lua 脚本执行失败 |
| 报错 | NOSCRIPT No matching script |
| 根因 | 脚本加载编码错误,导致 SHA1 不匹配 |
| 快速修复 | 修正容器 locale 为 UTF-8 |
| 稳健方案 | 代码中显式使用 UTF-8 |
| 预防 | 构建时检测 file.encoding,避免中文脚本 |
八、附录:排查命令
1 | # Redis 是否缓存脚本 |
九、结语
Redis 与 Redisson 本身没有问题,根因在于系统层的字符编码不一致。
经验教训:
- 容器 locale 是 JVM 编码的隐性依赖;
- 生产环境应强制统一
file.encoding=UTF-8; - 加载脚本或配置文件时,应显式指定编码;
- 避免在 Lua 等脚本中使用中文注释,以降低环境依赖。