EVAL 与 EVALSHA 的区别:Redis Lua 脚本调用的两种方式

EVAL 与 EVALSHA 的区别:Redis Lua 脚本调用的两种方式

基本概念

Redis 提供了两种方式执行 Lua 脚本:EVALEVALSHA。两者的本质区别在于脚本的传递方式——一个是完整脚本内容,一个是脚本的 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
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容