验证EVAL一段从未手动LOAD过的脚本会不会自动进入缓存,以及假SHA和SCRIPT FLUSH之后EVALSHA会报什么错误,理解脚本缓存的生命周期。
很多资料强调要先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之后,变成什么
完全没有手动调用过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同一段脚本文本也完全没问题,服务器会自己去重缓存。
缓存这件事的另一面:它随时可能失效。
结果EVALSHA 一个编造的40位假SHA: 报错 "No matching script. Please use EVAL." 执行SCRIPT FLUSH之后: SCRIPT EXISTS [sha] (之前缓存过的脚本): [False] EVALSHA sha: 同样报错 "No matching script. Please use EVAL."
NOSCRIPT类错误(说明缓存里没有,可能是刚重启、可能是被SCRIPT FLUSH清空、也可能是主从failover换了一个从来没跑过这段脚本的新主库),就自动降级成完整的EVAL重新发一次(同时也会把它重新缓存)。脚本缓存是纯内存级别、不持久化的(跟上一篇Functions可以持久化的函数库刚好相反),Redis重启、SCRIPT FLUSH、主从切换到一个新节点,都会让缓存清空,这也是为什么依赖EVALSHA的代码永远不能省略"缓存未命中就退回EVAL"这一步兼容逻辑。
每一步都是本文上面用SCRIPT/EVAL相关命令跑出来的原始结果。
SCRIPT LOAD/EXISTS/FLUSH / EVAL / EVALSHA:验证脚本自动缓存与缓存失效场景