Redis 的 EVAL 和 EVALSHA 命令用法
【摘要】 Redis 的 EVAL 和 EVALSHA 命令用于执行 Lua 脚本,它们是 Redis 实现复杂原子操作的核心工具。以下是它们的详细使用方法、区别、优化技巧及实战示例。 1. EVAL 命令 语法EVAL script numkeys key [key ...] arg [arg ...]script:Lua 脚本内容(字符串形式)。numkeys:脚本中使用的 Redis 键的数量(...
Redis 的 EVAL
和 EVALSHA
命令用于执行 Lua 脚本,它们是 Redis 实现复杂原子操作的核心工具。以下是它们的详细使用方法、区别、优化技巧及实战示例。
1. EVAL
命令
语法
EVAL script numkeys key [key ...] arg [arg ...]
script
:Lua 脚本内容(字符串形式)。numkeys
:脚本中使用的 Redis 键的数量(如KEYS[1]
,KEYS[2]
)。key [key ...]
:具体的 Redis 键(在脚本中通过KEYS
数组访问)。arg [arg ...]
:额外参数(在脚本中通过ARGV
数组访问)。
关键点
-
KEYS
和ARGV
KEYS
数组存储传入的键(如KEYS[1]
对应第一个键)。ARGV
数组存储额外参数(如ARGV[1]
对应第一个参数)。
-
原子性
整个 Lua 脚本会原子性执行,期间不会被其他命令打断。 -
返回值
- 脚本的最后一个表达式的返回值会作为
EVAL
的结果。 - 支持返回字符串、数字、数组(Redis 多返回值)等。
- 脚本的最后一个表达式的返回值会作为
示例
(1) 基本操作:设置键值
# 脚本:设置 key1 的值为 "hello",并返回 "OK"
127.0.0.1:6379> EVAL "redis.call('SET', KEYS[1], ARGV[1]); return 'OK'" 1 key1 "hello"
"OK"
127.0.0.1:6379> GET key1
"hello"
(2) 条件判断:如果键不存在则设置
# 脚本:如果 key2 不存在,则设置值为 "world"
127.0.0.1:6379> EVAL "if redis.call('EXISTS', KEYS[1]) == 0 then return redis.call('SET', KEYS[1], ARGV[1]) else return 0 end" 1 key2 "world"
"OK" # 返回 "OK" 表示设置成功
(3) 批量操作:哈希表更新
# 脚本:同时更新哈希表 user:1000 的 name 和 age 字段
127.0.0.1:6379> EVAL "
redis.call('HSET', KEYS[1], 'name', ARGV[1]);
redis.call('HSET', KEYS[1], 'age', ARGV[2]);
return redis.call('HGETALL', KEYS[1]);
" 1 user:1000 "Alice" "30"
1) "name"
2) "Alice"
3) "age"
4) "30"
2. EVALSHA
命令
为什么需要 EVALSHA
?
- 减少网络开销:
EVAL
每次都需要传输完整的脚本内容,而EVALSHA
只需传输脚本的 SHA1 摘要。 - 缓存脚本:Redis 会缓存已执行的脚本,后续通过
EVALSHA
快速调用。
语法
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
sha1
:脚本的 SHA1 摘要(通过SCRIPT LOAD
或之前EVAL
的返回值获取)。- 其他参数与
EVAL
相同。
使用步骤
- 加载脚本(获取 SHA1):
127.0.0.1:6379> SCRIPT LOAD "return redis.call('GET', KEYS[1])" "5332031c6b470dc5a0dd9b4bf2030dea6d65de91"
- 执行脚本:
127.0.0.1:6379> EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 1 mykey
示例
(1) 缓存脚本并复用
# 1. 加载脚本(返回 SHA1)
127.0.0.1:6379> SCRIPT LOAD "return redis.call('INCR', KEYS[1])"
"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"
# 2. 使用 EVALSHA 执行
127.0.0.1:6379> EVALSHA a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 1 counter
(integer) 1
127.0.0.1:6379> EVALSHA a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 1 counter
(integer) 2
(2) 处理 NOSCRIPT
错误
如果脚本未加载,EVALSHA
会返回 (error) NOSCRIPT
。此时需回退到 EVAL
:
# 模拟未加载的脚本
127.0.0.1:6379> EVALSHA "invalid_sha1" 1 key
(error) NOSCRIPT No matching script. Please use EVAL.
# 回退到 EVAL
127.0.0.1:6379> EVAL "return redis.call('GET', KEYS[1])" 1 key
(nil)
3. EVAL
vs EVALSHA
对比
特性 | EVAL |
EVALSHA |
---|---|---|
脚本传输 | 每次传输完整脚本 | 仅传输 SHA1 摘要 |
性能 | 较低(网络开销大) | 较高(适合频繁调用) |
缓存 | 不缓存 | Redis 自动缓存已执行的脚本 |
适用场景 | 一次性脚本或调试 | 生产环境高频调用的脚本 |
4. 高级技巧
(1) 脚本调试
- 日志输出:使用
redis.log
打印调试信息(需 Redis 配置loglevel debug
):redis.log(redis.LOG_WARNING, "Debug: KEYS[1]=" .. tostring(KEYS[1]))
- 本地测试:在 Lua 演示环境 中测试逻辑。
(2) 错误处理
- 使用
redis.pcall
捕获错误(避免脚本中断):local status, err = redis.pcall('GET', KEYS[1]) if not status then return "Error: " .. err end return status
(3) 多返回值
- Lua 脚本可通过
return {val1, val2, ...}
返回多个值:127.0.0.1:6379> EVAL "return {redis.call('GET', KEYS[1]), redis.call('TTL', KEYS[1])}" 1 mykey 1) "hello" 2) (integer) -1 # 键未设置过期时间
5. 实战案例
案例 1:分布式锁(安全释放)
-- 脚本:只有锁的持有者才能释放锁
local lockKey = KEYS[1]
local lockValue = ARGV[1]
local currentValue = redis.call('GET', lockKey)
if currentValue == lockValue then
return redis.call('DEL', lockKey)
else
return 0 -- 非持有者,拒绝释放
end
调用方式:
127.0.0.1:6379> SET lock:order "client1" EX 10 # 先获取锁
OK
127.0.0.1:6379> EVAL "..." 1 lock:order "client1" # 释放锁
(integer) 1
案例 2:限流器
-- 脚本:60秒内最多允许100次请求
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call('GET', key)
if current and tonumber(current) >= limit then
return 0 -- 限流
end
redis.call('INCR', key)
if not current then
redis.call('EXPIRE', key, window)
end
return 1 -- 允许
调用方式:
127.0.0.1:6379> EVAL "..." 1 api:rate_limit 100 60
6. 注意事项
- 脚本长度限制:默认脚本最大为 512MB(可通过
config set lua-time-limit
调整)。 - 阻塞风险:避免在脚本中执行耗时操作(如循环大列表)。
- 键命名规范:建议使用
:
分隔命名空间(如user:1000:profile
)。 - Redis 版本兼容性:确保所有 Redis 节点运行相同版本(避免 SHA1 不匹配)。
总结
命令 | 适用场景 | 优化建议 |
---|---|---|
EVAL |
一次性脚本、调试 | 避免生产环境频繁使用 |
EVALSHA |
生产环境高频调用的脚本 | 结合 SCRIPT LOAD 预加载 |
通过合理使用 EVAL
和 EVALSHA
,可以显著提升 Redis 在复杂场景下的性能和一致性。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)