Lua 脚本
EVAL 基础
脚本以原子方式运行:脚本执行期间不会执行其他命令。KEYS[n] 和 ARGV[n] 从 1 开始索引。
# EVAL script numkeys key [key ...] arg [arg ...]
EVAL "return 'hello'" 0
# Access keys and arguments
EVAL "return {KEYS[1], ARGV[1]}" 1 mykey myvalue
# Call Redis commands from Lua
EVAL "redis.call('SET', KEYS[1], ARGV[1]); return 1" 1 counter 42
# redis.call vs redis.pcall
# redis.call → raises error on failure (propagates to client)
# redis.pcall → returns error table, script continues
EVAL "
local v = redis.call('GET', KEYS[1])
if v then
return tonumber(v) + 1
end
return 1
" 1 mykey
原子比较并设置
经典使用场景:无需事务即可原子地进行读-改-写。
# Atomic CAS: set new value only if current equals expected
EVAL "
local cur = redis.call('GET', KEYS[1])
if cur == ARGV[1] then
redis.call('SET', KEYS[1], ARGV[2])
return 1
end
return 0
" 1 mykey expected_value new_value
# Rate limiter: allow N requests per window
EVAL "
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call('INCR', key)
if current == 1 then
redis.call('EXPIRE', key, window)
end
if current > limit then
return 0
end
return 1
" 1 ratelimit:user:42 100 60
使用 Lua 实现分布式锁
# Acquire lock (SET NX EX is atomic but Lua allows custom logic)
# SET key value NX EX seconds
SET lock:resource unique_token NX EX 30
# Release lock: only release if we own it (atomic check-and-delete)
EVAL "
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
" 1 lock:resource unique_token
# Extend lock TTL atomically
EVAL "
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('PEXPIRE', KEYS[1], ARGV[2])
else
return 0
end
" 1 lock:resource unique_token 30000
SCRIPT LOAD 与 EVALSHA
加载一次脚本并通过其 SHA1 哈希执行,以减少网络开销。
# Load script into server cache, returns SHA1
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
# Returns: "e0e1f9fabfa9d353eca4c6f67a4a3c35c92fa08d"
# Execute by SHA1
EVALSHA e0e1f9fabfa9d353eca4c6f67a4a3c35c92fa08d 1 mykey
# Check if script is cached
SCRIPT EXISTS sha1 sha2 sha3 # returns 0/1 per sha
# Flush all cached scripts
SCRIPT FLUSH
# Using EVALSHA in Node.js (ioredis)
const sha = await client.script("load", luaScript);
const result = await client.evalsha(sha, 1, "mykey");
Lua 最佳实践
| 最佳实践 | 原因 |
|---|---|
| 始终通过 KEYS[] 传递键 | Redis 集群键路由所必需 |
| 保持脚本简短 | 长脚本会阻塞 Redis(单线程) |
| 生产中使用 EVALSHA | 避免每次调用重新发送脚本字节 |
| 脚本中无 I/O 或睡眠 | 会阻塞事件循环 |
| 使用 tonumber()/tostring() | Redis 返回批量字符串,按需转换类型 |