Redis Evalsha 命令详解:提升脚本执行效率的利器
在使用 Redis 作为缓存或数据存储时,我们常会遇到需要执行多个操作的场景。比如,先检查某个键是否存在,如果不存在就设置一个默认值,再返回结果。这类操作如果拆成多个命令,不仅网络往返多,性能也差。而 Redis 提供了 EVAL 和 EVALSHA 命令,允许我们在服务器端执行 Lua 脚本,实现原子性操作。
今天我们就来深入聊聊 Redis Evalsha 命令。它与 EVAL 一样,都是执行 Lua 脚本的接口,但 Evalsha 有一个关键优势:它使用脚本的 SHA1 哈希值来调用已缓存的脚本,避免重复传输脚本内容,从而大幅提升执行效率。
为什么需要 Evalsha 命令?
想象一下,你在开发一个电商系统,每次用户下单时,都要做如下操作:
- 检查商品库存是否充足;
- 如果充足,库存减 1;
- 记录订单信息;
- 返回结果。
如果用 Redis 命令逐条执行,至少需要 4 次网络通信。而如果把这些逻辑写成一个 Lua 脚本,通过 EVAL 或 Evalsha 一次发送,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 个 keycounter:键名,即我们要操作的计数器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
解决方法:确保 numkeys 与 KEYS 数量一致。
实际项目中的最佳实践
在真实项目中,我们通常会这样使用 Evalsha:
- 脚本预加载:在应用启动时,通过
SCRIPT LOAD将常用脚本加载到 Redis 缓存中。 - 脚本版本管理:为每个脚本生成唯一的 SHA1,结合版本号管理,避免冲突。
- 客户端封装:在 Java、Python 等语言的客户端中,封装
Evalsha调用逻辑,自动处理脚本加载与缓存。 - 错误重试机制:如果
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 的真正价值所在。