Redis Evalsha 命令(保姆级教程)

Redis Evalsha 命令详解:提升脚本执行效率的利器

在使用 Redis 作为缓存或数据存储时,我们常会遇到需要执行多个操作的场景。比如,先检查某个键是否存在,如果不存在就设置一个默认值,再返回结果。这类操作如果拆成多个命令,不仅网络往返多,性能也差。而 Redis 提供了 EVALEVALSHA 命令,允许我们在服务器端执行 Lua 脚本,实现原子性操作。

今天我们就来深入聊聊 Redis Evalsha 命令。它与 EVAL 一样,都是执行 Lua 脚本的接口,但 Evalsha 有一个关键优势:它使用脚本的 SHA1 哈希值来调用已缓存的脚本,避免重复传输脚本内容,从而大幅提升执行效率。


为什么需要 Evalsha 命令?

想象一下,你在开发一个电商系统,每次用户下单时,都要做如下操作:

  1. 检查商品库存是否充足;
  2. 如果充足,库存减 1;
  3. 记录订单信息;
  4. 返回结果。

如果用 Redis 命令逐条执行,至少需要 4 次网络通信。而如果把这些逻辑写成一个 Lua 脚本,通过 EVALEvalsha 一次发送,Redis 会原子地执行整个脚本,不仅减少延迟,还能保证数据一致性。

但问题来了:EVAL 每次都需要传入完整的 Lua 脚本代码,如果脚本很长,传输开销大。而 Evalsha 就是为了解决这个问题而生——它只传一个 40 位的 SHA1 哈希值,Redis 会根据这个哈希值从内部缓存中查找对应的脚本,直接执行。

✅ 简单来说:EVAL 是“传代码”,Evalsha 是“传指纹”。


Evalsha 命令的基本语法与参数说明

Redis Evalsha 命令 的语法如下:

EVALSHA sha1 numkeys key [key ...] arg [arg ...]
参数 说明
sha1 脚本的 SHA1 哈希值,必须是 40 位十六进制字符串
numkeys 后面要传入的 key 的数量,必须是整数
key [key ...] 传递给脚本的 key,数量必须与 numkeys 一致
arg [arg ...] 传递给脚本的参数,可以是任意数量

⚠️ 注意:Evalsha 只能调用 Redis 服务器中已经缓存过的脚本。如果脚本未缓存,会返回错误。


如何使用 Evalsha 命令?分步实战

我们通过一个实际例子来演示。假设我们要实现一个“计数器加一并返回新值”的功能,同时支持并发安全。

第一步:编写 Lua 脚本

-- 脚本功能:给指定键的值加 1,返回新值
-- 输入参数:key 名称,可选的增量值(默认为 1)
-- 返回值:新的计数值
local key = KEYS[1]           -- 获取第一个 key,即计数器键名
local increment = tonumber(ARGV[1]) or 1  -- 获取增量,若无则默认为 1
local current = redis.call("GET", key)    -- 从 Redis 获取当前值
if current == false then
    current = 0  -- 如果键不存在,初始化为 0
end
local new_value = current + increment     -- 计算新值
redis.call("SET", key, new_value)         -- 写回新值
return new_value                          -- 返回结果

💡 提示:KEYS 是一个数组,用来接收传入的键;ARGV 是一个数组,接收传入的参数。redis.call() 是 Lua 脚本中调用 Redis 命令的接口。

第二步:将脚本提交给 Redis 并获取 SHA1

我们使用 SCRIPT LOAD 命令将脚本加载到 Redis 的脚本缓存中,并返回其 SHA1 哈希值。

SCRIPT LOAD "local key = KEYS[1] local increment = tonumber(ARGV[1]) or 1 local current = redis.call(\"GET\", key) if current == false then current = 0 end local new_value = current + increment redis.call(\"SET\", key, new_value) return new_value"

Redis 返回类似:

f0c61a72f1763622d64f979263a995606c1828a2

这个 40 位的字符串就是脚本的唯一指纹,后续我们就可以用它来调用 Evalsha


第三步:使用 Evalsha 执行脚本

现在,我们用 Evalsha 调用这个脚本:

EVALSHA f0c61a72f1763622d64f979263a995606c1828a2 1 counter 5
  • f0c61a72f1763622d64f979263a995606c1828a2:脚本的 SHA1 哈希值
  • 1:表示后面有 1 个 key
  • counter:键名,即我们要操作的计数器
  • 5:传给脚本的参数,表示增量为 5

如果 counter 键原本不存在,Redis 会先设为 0,加 5 后返回 5。


Evalsha 与 EVAL 的对比:性能差异分析

特性 EVAL Evalsha
是否需要传脚本内容
网络开销 高(脚本内容可能很长) 低(仅传 40 字符)
执行效率 低(每次都要解析脚本) 高(直接从缓存执行)
适用场景 一次性脚本、调试 生产环境、高频调用
脚本缓存机制 不缓存(每次重新解析) 会缓存,支持复用

✅ 举个生活中的比喻:
EVAL 就像你每次去餐厅点菜,都要把菜单完整地读一遍给服务员听;
Evalsha 就像你只说“我要点上次那道菜”,服务员立刻知道你点的是什么。

在高并发系统中,Evalsha 可以显著降低网络延迟,提升吞吐量。


常见错误与解决方案

错误 1:NOSCRIPT 错误

当你调用 Evalsha 但 Redis 中没有对应的脚本缓存时,会返回:

NOSCRIPT No matching script. Please use EVAL.

解决方法:先使用 SCRIPT LOAD 加载脚本,再调用 Evalsha

错误 2:SHA1 值错误

如果你拼错了 SHA1 值,Redis 会报错:

NOSCRIPT No matching script. Please use EVAL.

解决方法:检查 SHA1 值是否正确,建议用程序生成并缓存。

错误 3:key 数量不匹配

如果 numkeys 和实际传入的 key 数量不一致,Redis 会返回:

ERR wrong number of arguments for 'evalsha' command

解决方法:确保 numkeysKEYS 数量一致。


实际项目中的最佳实践

在真实项目中,我们通常会这样使用 Evalsha

  1. 脚本预加载:在应用启动时,通过 SCRIPT LOAD 将常用脚本加载到 Redis 缓存中。
  2. 脚本版本管理:为每个脚本生成唯一的 SHA1,结合版本号管理,避免冲突。
  3. 客户端封装:在 Java、Python 等语言的客户端中,封装 Evalsha 调用逻辑,自动处理脚本加载与缓存。
  4. 错误重试机制:如果 Evalsha 失败,可自动降级为 EVAL,保证系统可用性。

📌 小技巧:你可以用 SCRIPT EXISTS sha1 查询脚本是否已缓存,避免无效调用。


如何在代码中安全使用 Evalsha?

以 Python 为例,使用 redis-py 客户端:

import redis

client = redis.Redis(host='localhost', port=6379, db=0)

script_code = '''
local key = KEYS[1]
local increment = tonumber(ARGV[1]) or 1
local current = redis.call("GET", key)
if current == false then current = 0 end
local new_value = current + increment
redis.call("SET", key, new_value)
return new_value
'''

sha1 = client.script_load(script_code)

result = client.evalsha(sha1, 1, "user:login:count", 1)
print(f"新计数值: {result}")

✅ 优点:脚本只传一次,后续调用仅传 SHA1,效率极高。


总结:为什么你该用 Evalsha 命令?

Redis Evalsha 命令 不只是一个命令,更是一种性能优化策略。它通过脚本缓存机制,让高频执行的 Lua 脚本不再受网络传输的拖累。

  • 它适合在需要原子操作、高并发、低延迟的场景中使用;
  • 它能有效减少 Redis 与客户端之间的通信次数;
  • 它是构建高性能缓存系统、分布式锁、计数器等组件的核心工具。

如果你正在构建一个对性能有要求的系统,Redis Evalsha 命令 绝对值得你深入了解并熟练掌握。

记住:脚本不是越复杂越好,而是要“一次加载,多次复用”。这才是 Evalsha 的真正价值所在。