Redis的Lua脚本不能自动在从节点执行读操作,因其EVAL/EVALSHA命令默认重定向至主节点;脚本若含写命令或非确定性操作(如math.random、KEYS),Redis即拒绝在从节点执行,必须人工确保脚本纯读、客户端启用READONLY模式并连接有效副本。

Redis 的 Lua 脚本**不能自动在从节点执行读操作**,因为 EVAL 和 EVALSHA 命令在主从架构中默认被重定向到主节点——即使你连接的是从节点,只要脚本里包含写命令(哪怕只是潜在的),Redis 就会拒绝在从节点执行,并返回 (error) BUSY Redis is busy running a script. You can only run SCRIPT KILL or SHUTDOWN NOSAVE. 或更常见的 (error) READONLY You can't write against a read only slave.。这不是配置问题,而是设计约束。
为什么 Lua 脚本无法“自然”走读写分离
Lua 脚本的执行原子性要求 Redis 必须能预判其读写意图。但 Redis 2.6+ 的脚本引擎**不解析脚本内容做静态分析**,而是依据运行时实际调用的命令动态判断:一旦脚本内调用 SET、INCR、HSET 等任意写命令(或调用 redis.call() 执行了写命令),整个脚本就被视为“写操作”,强制路由到主节点。
- 即使脚本只读(如纯
GET+EXISTS),若未显式声明只读上下文,部分客户端(如旧版 Jedis)仍可能因连接池复用或 pipeline 行为误发到主节点 -
READONLY命令仅影响后续普通命令,对EVAL无效——它不能让脚本“变读” - Redis Cluster 模式下,
READONLY根本不可用,EVAL一律重定向到 key 所在槽的主节点
真正可行的只读 Lua 脚本执行路径
必须满足两个硬条件:脚本本身只读 + 客户端连接明确指向从节点 + 连接已启用只读模式。
- 脚本必须**确定不含任何写命令**(包括
redis.call('SET', ...)、redis.pcall('DEL', ...)等),可用SCRIPT LOAD预加载后检查返回 SHA,再用SCRIPT EXISTS确认;但最稳妥是人工审计 - 客户端连接从节点后,**必须先执行
READONLY命令**(注意:不是配置项,是实时命令),否则从节点仍可能拒绝读请求(尤其当主节点宕机、slave-serve-stale-data no时) - Spring Boot 中使用 Lettuce,需配置
ReadFrom.REPLICA并确保LettuceConnectionFactory的clientConfig生效,且脚本调用走的是该 factory 创建的RedisTemplate - 避免在脚本中使用
KEYS、SCAN等非确定性命令——它们虽不写,但可能触发慢查询,影响从节点稳定性
常见错误与绕过陷阱
典型报错:(error) ERR Error running script (call to f_...): @user_script:3: @user_script: 3: Write commands not allowed after non deterministic commands.
- 原因:脚本里用了
math.random()、redis.time()或redis.call('KEYS', '*')—— 这些会让 Redis 认定脚本“不可缓存、不可复制”,直接拒绝在从节点执行 - 修复:改用确定性替代方案,例如用传入参数代替
math.random(),用客户端时间戳代替redis.time(),用已知 key 列表代替KEYS - 另一个坑:Lettuce 的
ReadFrom.REPLICA默认策略是“随机选一个可用 replica”,但如果某从节点网络延迟高或master_link_status为down,请求可能失败;建议配合ReadFrom.REPLICA_PREFERRED(优先 replica,失败降级 master)或自定义健康检查 - 别依赖
slave-read-only yes配置来“保证”脚本只读——它只防直接SET,不干预 Lua 解析逻辑
生产环境建议的最小安全链路
从节点执行只读 Lua 脚本不是“开了个开关就能用”的功能,而是一条需要端到端控制的链路:
- Redis 配置:确认从节点
slave-read-only yes、slave-serve-stale-data yes(允许主断连后继续服务旧数据) - 脚本发布:用
SCRIPT LOAD预热所有只读脚本,记录 SHA,在应用启动时校验存在性 - 客户端:Lettuce 配置
ReadFrom.REPLICA_PREFERRED+ 显式executeCommand("READONLY")(连接初始化时) - 监控:通过
INFO replication检查master_last_io_seconds_ago,确保从节点复制延迟在业务容忍范围内(如
最关键的忽略点:很多人以为只要脚本没写命令,连上从节点就能跑——但忘了 READONLY 是连接级命令,每次新建连接都得重发;连接池里的连接若未初始化该状态,第一次 EVAL 就会失败。

















