EVAL 与 EVALSHA 的区别:Redis Lua 脚本调用的两种方式
基本概念
Redis 提供了两种方式执行 Lua 脚本:EVAL 和 EVALSHA。两者的本质区别在于脚本的传递方式——一个是完整脚本内容,一个是脚本的 SHA1 签名。
EVAL 命令
EVAL script numkeys key [key ...] arg [arg ...]
用法:将完整的 Lua 脚本代码作为参数发送给 Redis。
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalue
特点:
– 每次调用都传递完整的脚本源码
– 每次调用都会编译脚本(Redis 有编译缓存,但网络传输开销大)
– 方便调试和临时执行
EVALSHA 命令
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
用法:将脚本的 SHA1 签名(40 位十六进制字符串)发送给 Redis。
-- 先通过 SCRIPT LOAD 加载脚本并获取 SHA
SCRIPT LOAD "return redis.call('SET', KEYS[1], ARGV[1])"
-- 返回: "b8059fd95694a2b088fa3d9f8a083d4b368f9da3"
-- 后续通过 SHA 调用,无需传输源码
EVALSHA b8059fd95694a2b088fa3d9f8a083d4b368f9da3 1 mykey myvalue
特点:
– 只传递签名,网络传输量极小
– 无需重复编译(脚本已缓存)
– 性能更高
核心区别一览
| 对比项 | EVAL | EVALSHA |
|---|---|---|
| 传输内容 | 完整脚本源码 | SHA1 签名(40 字节) |
| 编译 | 需要(但有缓存) | 不需要(已存在缓存) |
| 网络开销 | 大(脚本内容) | 很小 |
| 依赖条件 | 无需提前加载 | 脚本必须先加载到缓存 |
| 首次性能 | 与 EVALSHA 接近 | 需要先 SCRIPT LOAD |
| 后续性能 | 相同脚本也需传源码 | 只需传签名 |
| 异常情况 | 一般不会失败 | 可能报 NOSCRIPT 错误 |
生产环境推荐模式:SCRIPT LOAD + EVALSHA
import redis
r = redis.Redis()
# 脚本内容
script_content = """
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = redis.call('GET', key)
if current and tonumber(current) >= limit then
return 0
end
redis.call('INCR', key)
redis.call('EXPIRE', key, ARGV[2])
return 1
"""
# 生产环境推荐做法:预先加载
script_sha = r.script_load(script_content)
# 创建可重用的执行函数
def limit_access(key, limit, ttl):
try:
result = r.evalsha(script_sha, 1, key, limit, ttl)
return result == 1
except redis.exceptions.NoScriptError:
# 如果 SHA 不存在(如 Redis 重启后缓存丢失),自动回退到 EVAL
result = r.eval(script_content, 1, key, limit, ttl)
return result == 1
EVALSHA 的异常处理:NOSCRIPT 错误
当 Redis 重启或脚本被 SCRIPT FLUSH 清除后,使用 EVALSHA 会收到错误:
(error) NOSCRIPT No matching script. Please use EVAL.
处理方法
方法一:显式回退
def evalsha_safe(r, sha, script, keys, args):
try:
return r.evalsha(sha, len(keys), *keys, *args)
except NoScriptError:
return r.eval(script, len(keys), *keys, *args)
方法二:先在 Redis 端注册脚本
在应用启动时统一执行 SCRIPT LOAD,确保所有脚本在运行前已加载。
性能对比测试
脚本大小约 200 字节的网络场景:
EVAL 调用 → ~0.5ms 延迟(网络传输 200 字节 + 编译)
EVALSHA 调用 → ~0.2ms 延迟(网络传输 40 字节,无需重新编译)
节省:60%+ 的网络传输时间
面试要点
- EVAL:携带源码,适合开发测试;EVALSHA:携带签名,适合生产环境
- EVALSHA 可能失败:Redis 重启或 SCRIPT FLUSH 后,需要降级到 EVAL
- 推荐模式:先
SCRIPT LOAD注册,调用EVALSHA,异常时 fallback 到EVAL - Redis 内部对 EVAL 也有编译缓存(脚本内容和 SHA 的映射表),但每次仍需传输源码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容