Redis Lua 脚本异常:“NOSCRIPT No matching script” 故障分析与修复

一、事件背景

项目使用 Spring Data Redis + Redisson 执行 Lua 脚本,实现分布式原子更新逻辑。

示例代码:

1
2
3
4
5
DefaultRedisScript<List> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(
new ClassPathResource("scripts/cas.lua")
));
redisScript.setResultType(List.class);

Redis 为单机模式部署。
镜像重构后应用启动正常,但执行 Lua 脚本时报错。

二、问题现象

报错堆栈

1
2
3
Caused by: org.redisson.client.RedisException: NOSCRIPT No matching script. Please use EVAL..
channel: [id: 0xad12ab10, L:/172.18.0.18:56912 - R:172.17.0.1:6379]
command: (EVALSHA), params: [f21f6c8c24daabf..., ...]

含义:Redis 未找到匹配的脚本 SHA1 缓存,因此 Redisson 无法执行 EVALSHA

三、初步分析

Redis 的 Lua 执行过程:

  1. 客户端首次使用 EVAL 发送脚本;
  2. Redis 编译脚本,缓存其内容与 SHA1;
  3. 后续客户端使用 EVALSHA 直接执行;
  4. 若 SHA1 不存在,返回 NOSCRIPT

因此,本次报错说明:

Redis 并未加载到客户端所认为的“同一份脚本内容”。

四、排查过程

1️⃣ 验证 Redis 是否缓存脚本

1
redis-cli SCRIPT EXISTS f21f6c8c24daabf...

返回:

1
1) (integer) 0

→ Redis 未缓存该 SHA1。

2️⃣ 检查 Lua 脚本内容

cas.lua 文件中含中文注释:

1
2
-- 校验版本号
local key = KEYS[1]

删除中文注释后再执行:

1
redis-cli --eval scripts/cas.lua

✅ Redis 能正常执行。
→ 表面上似乎“中文注释导致 Redis 编译失败”,但实际上:

Redis 支持 UTF-8 中文脚本,真正问题是客户端加载时字符编码被破坏,发送给 Redis 的内容与原文件不一致。

3️⃣ 检查容器环境编码

进入容器:

1
locale

输出:

1
2
3
4
locale: Cannot set LC_CTYPE...
LANG=zh_CN.UTF-8
LANGUAGE=zh_CN:zh
LC_ALL=zh_CN.UTF-8

虽然声明了 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
2
3
4
5
6
7
RUN apt-get update && \
apt-get install -y locales tzdata && \
rm -rf /var/lib/apt/lists/* && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 && \
echo "LANG=zh_CN.UTF-8" >> /etc/environment && \
echo "LC_ALL=zh_CN.UTF-8" >> /etc/environment

验证:

1
2
3
locale
java -XshowSettings:properties -version | grep file.encoding
# 应为 UTF-8

🧩 稳健方案(代码级)

避免依赖系统编码,手动读取脚本并指定 UTF-8:

1
2
3
4
5
6
7
8
String script = Files.readString(
Paths.get(new ClassPathResource("scripts/cas.lua").getURI()),
StandardCharsets.UTF_8
);

DefaultRedisScript<List> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(List.class);

此方法绕过 ResourceScriptSource,完全杜绝编码不一致问题。

七、经验总结

项目 内容
故障类型 Redis Lua 脚本执行失败
报错 NOSCRIPT No matching script
根因 脚本加载编码错误,导致 SHA1 不匹配
快速修复 修正容器 locale 为 UTF-8
稳健方案 代码中显式使用 UTF-8
预防 构建时检测 file.encoding,避免中文脚本

八、附录:排查命令

1
2
3
4
5
6
7
8
9
10
11
# Redis 是否缓存脚本
redis-cli SCRIPT EXISTS <sha1>

# 清空缓存脚本
redis-cli SCRIPT FLUSH

# 验证 Lua 编译
redis-cli --eval scripts/test.lua

# 查看 JVM 编码
java -XshowSettings:properties -version | grep file.encoding

九、结语

Redis 与 Redisson 本身没有问题,根因在于系统层的字符编码不一致。

经验教训:

  • 容器 locale 是 JVM 编码的隐性依赖;
  • 生产环境应强制统一 file.encoding=UTF-8
  • 加载脚本或配置文件时,应显式指定编码;
  • 避免在 Lua 等脚本中使用中文注释,以降低环境依赖。