bigywater.tech
Redis · 35

Lua 脚本缓存

验证EVAL一段从未手动LOAD过的脚本会不会自动进入缓存,以及假SHA和SCRIPT FLUSH之后EVALSHA会报什么错误,理解脚本缓存的生命周期。

自动缓存
EVAL会自动把脚本存进缓存,不需要手动LOAD
NOSCRIPT
假SHA和SCRIPT FLUSH后报同一个错误
不持久化
脚本缓存纯内存,重启/FLUSH/切主都会清空

做的事:验证脚本缓存到底是"手动"还是"自动"的

很多资料强调要先SCRIPT LOAD再EVALSHA,验证是不是必须这么做。

代码script2 = "return redis.call('GET', KEYS[1]) .. '-v2'" sha2 = hashlib.sha1(script2.encode()).hexdigest() -- 自己算出的SHA1 print(r.script_exists(sha2)) -- 从未LOAD过,这时候应该是False print(r.eval(script2, 1, "cachekey")) -- 直接EVAL,不LOAD print(r.script_exists(sha2)) -- EVAL之后,变成什么

验证:EVAL一次之后,这段脚本自动进了缓存

完全没有手动调用过SCRIPT LOAD。

结果SCRIPT EXISTS [sha2] before EVAL: [False] EVAL script2 result: "hello-v2" SCRIPT EXISTS [sha2] after EVAL: [True] EVALSHA sha2 (直接用SHA调用,脚本文本都不用发): "hello-v2"

验证:EVAL命令本身在执行脚本的同时,会顺带把这段脚本存进服务器内部的一张"脚本缓存表"里,key就是这段脚本文本的SHA1——不需要额外调用SCRIPT LOAD。SCRIPT LOAD存在的意义是:如果你想在真的执行之前,先确认脚本已经进了缓存(比如为了后续只用EVALSHA、省去每次都发完整脚本文本的网络开销),可以显式调用它;但如果你不在意这点优化,直接反复EVAL同一段脚本文本也完全没问题,服务器会自己去重缓存。

验证:假SHA和SCRIPT FLUSH之后,都会报同一个错误

缓存这件事的另一面:它随时可能失效。

结果EVALSHA 一个编造的40位假SHA: 报错 "No matching script. Please use EVAL." 执行SCRIPT FLUSH之后: SCRIPT EXISTS [sha] (之前缓存过的脚本): [False] EVALSHA sha: 同样报错 "No matching script. Please use EVAL."
这条错误消息本身就在提示正确的应对方式:"用EVALSHA失败了,就换回用EVAL"——这正是官方客户端库和生产环境里推荐的容错模式:先尝试EVALSHA(省网络流量),如果服务器返回NOSCRIPT类错误(说明缓存里没有,可能是刚重启、可能是被SCRIPT FLUSH清空、也可能是主从failover换了一个从来没跑过这段脚本的新主库),就自动降级成完整的EVAL重新发一次(同时也会把它重新缓存)。脚本缓存是纯内存级别、不持久化的(跟上一篇Functions可以持久化的函数库刚好相反),Redis重启、SCRIPT FLUSH、主从切换到一个新节点,都会让缓存清空,这也是为什么依赖EVALSHA的代码永远不能省略"缓存未命中就退回EVAL"这一步兼容逻辑。

分步演示:脚本缓存的生命周期

每一步都是本文上面用SCRIPT/EVAL相关命令跑出来的原始结果。

EVAL自动缓存,缓存又随时可能失效
这一步新验证的状态

    本文用到的工具

    • 本机 Redis 8.10.1
    • SCRIPT LOAD/EXISTS/FLUSH / EVAL / EVALSHA:验证脚本自动缓存与缓存失效场景
    京ICP备2026067502号-1京公网安备11010502063487号