redis因主从一致性要求强制拦截非确定性命令组合:randomkey、time等返回值不可重现,若其后接set等写命令,会导致主从生成不同写序列而数据分裂。

为什么RANDOMKEY、TIME这类命令在Lua里一用就报错
Redis 报 (error) ERR Error running script (call to f_...): @user_script:3: user_script:3: Write command attempted after non deterministic command,不是脚本写错了,而是它检测到你先调了 RANDOMKEY 或 TIME,后面又试图执行 SET 这类写命令。Redis 强制拦截,因为这种组合会破坏主从一致性。
根本原因在于:主库执行一次脚本产生一组写命令(比如 SET a 1、INCR b),这些命令会被记录进 AOF 并同步给从库;但如果脚本里有随机逻辑,同一段脚本在从库重放时可能生成**完全不同**的写命令序列——主库写的是 SET x 100,从库却写成了 SET y 200,数据直接分裂。
-
RANDOMKEY每次返回的 key 不确定,后续对它的操作(如DEL)目标不固定 -
TIME返回当前时间戳,主从执行时刻不同,结果必然不同 -
SRANDMEMBER、SMEMBERS等集合类命令虽不写数据,但输出无序,Redis 会自动对其结果做字典序排序再返回,仅限读场景;一旦后接写操作,仍触发拦截
math.random 在 Redis Lua 里为什么“不真随机”
Redis 替换了标准 Lua 的 math.random 和 math.randomseed,让每次脚本执行时,除非显式调用 math.randomseed(n),否则生成的伪随机数序列完全一致。这不是为了安全,而是为了**可重现性**——主库跑出的数字序列,从库必须一模一样,才能保证写入行为一致。
所以你在脚本里写 local r = math.random(1,100),不管在主库还是从库、今天还是明天执行,只要没调 math.randomseed,r 的值永远相同。这是 Redis 做的底层适配,不是 bug,是强制约定。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 想真正随机?不行。Redis 不允许任何不可重现的副作用
- 想模拟“随机选一个 key 处理”?改用
KEYS[1]或传入明确 key 名,别依赖RANDOMKEY - 需要带种子的随机?可以,但必须用固定值(如
math.randomseed(123)),且确保主从脚本传参和执行路径完全一致
从库 CPU 飙高,大概率是 Lua 脚本在超时重试
从库日志出现 Timeout while executing script,同时 top 显示 redis-server 占用 CPU 接近 100%,这不是脚本慢,是 Redis 在反复尝试中断+重试一个卡住的脚本。原因往往是脚本里调了 redis.call("GET", "missing_key"),而该 key 在从库尚未同步到位(全量刚完、增量未追上),导致命令阻塞,触发 lua-time-limit(默认 5000ms)超时。
- 主库不校验
lua-time-limit,只负责传播EVALSHA;从库严格受控,超时即中断,但中断失败会进入轮询重试循环 -
CLIENT LIST查cmd=evalsha+state=busy的连接,age大、idle小,基本就是它 - 临时缓解可用
CLIENT KILL ID <id></id>,但根治要改脚本:避免读取可能不存在的 key,或改用redis.pcall捕获并跳过 - 终极方案是配置
lua-replicate-commands no—— 反直觉,但它会让从库拒绝执行任何EVAL*,直接报READONLY You can't write against a read only slave.,彻底绕过执行环节
SCRIPT KILL 不是万能的,尤其不能乱用在已写入的脚本上
SCRIPT KILL 只对「纯读脚本」安全。如果你的脚本已经执行过 redis.call("SET", ...),再执行 SCRIPT KILL,Redis 无法回滚那部分写入——它只会中止后续逻辑,已落地的数据不会消失。这会造成主从状态不一致:主库完整执行完毕,从库只执行一半就被杀了。
更危险的是,SCRIPT KILL 在从库上可能根本不起作用。因为从库执行的是主库发来的 EVALSHA 命令,它没有本地脚本上下文,SCRIPT KILL 无法定位目标。此时只能靠 CLIENT KILL 杀连接,或等超时机制自己放弃。
- 别把
SCRIPT KILL当成事务 rollback,它只是粗暴中断,不保证原子性收尾 - 脚本设计阶段就要控制粒度:单个脚本只操作强相关 key,避免跨业务长流程
- 监控必须覆盖
INFO stats中的rejected_connections和expired_keys,它们常伴随 Lua 超时一起飙升










