redis 结合lua
【摘要】 Redis 结合 Lua 脚本是一种强大的组合,能够以原子性、高性能的方式实现复杂逻辑。Lua 脚本在 Redis 中会被当作一个整体执行,期间不会中断,从而保证操作的原子性。以下是详细说明和实战示例: 1. 为什么在 Redis 中使用 Lua?原子性:整个脚本作为一个事务执行,不会被其他命令打断。减少网络开销:将多个操作合并为一个脚本,减少客户端与 Redis 之间的通信次数。灵活性:支...
Redis 结合 Lua 脚本是一种强大的组合,能够以原子性、高性能的方式实现复杂逻辑。Lua 脚本在 Redis 中会被当作一个整体执行,期间不会中断,从而保证操作的原子性。以下是详细说明和实战示例:
1. 为什么在 Redis 中使用 Lua?
- 原子性:整个脚本作为一个事务执行,不会被其他命令打断。
- 减少网络开销:将多个操作合并为一个脚本,减少客户端与 Redis 之间的通信次数。
- 灵活性:支持条件判断、循环、复杂计算等逻辑。
- 性能高:Redis 单线程执行 Lua 脚本,避免多线程竞争。
2. 基本用法
执行 Lua 脚本
使用 EVAL
或 EVALSHA
(推荐)命令:
EVAL script numkeys key [key ...] arg [arg ...]
script
:Lua 脚本内容。numkeys
:脚本中涉及的 Redis 键的数量(如KEYS[1]
,KEYS[2]
)。key...
:键名(在脚本中通过KEYS
数组访问)。arg...
:额外参数(在脚本中通过ARGV
数组访问)。
示例:
# 计算两个数的和并存储到 Redis
127.0.0.1:6379> EVAL "return redis.call('SET', KEYS[1], tonumber(ARGV[1]) + tonumber(ARGV[2]))" 1 sum 10 20
OK
127.0.0.1:6379> GET sum
"30"
3. 常用 Lua + Redis API
Lua 函数 | 作用 |
---|---|
redis.call("CMD", ...) |
执行 Redis 命令(出错会抛异常) |
redis.pcall("CMD", ...) |
执行 Redis 命令(出错返回错误表) |
KEYS[1] , KEYS[2] |
访问传入的键 |
ARGV[1] , ARGV[2] |
访问传入的参数 |
4. 实战示例
(1) 原子性计数器限流
-- KEYS[1]: 限流 key(如用户 ID)
-- ARGV[1]: 限流阈值
-- ARGV[2]: 限流时间窗口(秒)
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
-- 计数器 +1
if current then
redis.call('INCR', key)
else
redis.call('SET', key, 1, 'EX', window)
end
return 1 -- 允许通过
调用方式:
127.0.0.1:6379> EVAL "..." 1 user:123:rate_limit 10 60 # 60秒内最多10次
(2) 分布式锁(带超时和自动释放)
-- KEYS[1]: 锁的 key
-- ARGV[1]: 锁的值(通常用唯一ID,如 UUID)
-- ARGV[2]: 锁的过期时间(毫秒)
local key = KEYS[1]
local value = ARGV[1]
local ttl = tonumber(ARGV[2])
-- 尝试获取锁
if redis.call('SET', key, value, 'NX', 'PX', ttl) then
return 1 -- 获取锁成功
else
return 0 -- 获取锁失败
end
调用方式:
127.0.0.1:6379> EVAL "..." 1 order_lock 12345 30000 # 30秒超时
(3) 安全释放锁(避免误删)
-- KEYS[1]: 锁的 key
-- ARGV[1]: 锁的值(必须匹配才能删除)
local key = KEYS[1]
local value = ARGV[1]
if redis.call('GET', key) == value then
return redis.call('DEL', key) -- 删除锁
else
return 0 -- 锁不属于当前客户端,不删除
end
调用方式:
127.0.0.1:6379> EVAL "..." 1 order_lock 12345
(4) 缓存防击穿(查询 DB 前先检查锁)
-- KEYS[1]: 数据 key
-- ARGV[1]: 锁的 key
-- ARGV[2]: 锁的过期时间(秒)
local dataKey = KEYS[1]
local lockKey = ARGV[1]
local lockTTL = tonumber(ARGV[2])
-- 1. 尝试从缓存获取数据
local data = redis.call('GET', dataKey)
if data then
return data -- 缓存命中
end
-- 2. 获取分布式锁(避免多个客户端同时查DB)
if redis.call('SET', lockKey, '1', 'NX', 'EX', lockTTL) then
-- 模拟查询DB(实际应替换为真实逻辑)
redis.call('SET', dataKey, 'db_data', 'EX', 60) -- 写入缓存
redis.call('DEL', lockKey) -- 释放锁
return 'db_data'
else
-- 未获取锁,短暂等待后重试(或返回空)
return nil
end
5. 性能优化
-
使用
EVALSHA
先通过SCRIPT LOAD
缓存脚本,后续用EVALSHA
执行,减少网络传输:127.0.0.1:6379> SCRIPT LOAD "return redis.call('GET', KEYS[1])" "a27e7e8a437a84c8d1f2b3c4d5e6f7g8h9i0j1k2" 127.0.0.1:6379> EVALSHA a27e7e8a437a84c8d1f2b3c4d5e6f7g8h9i0j1k2 1 mykey
-
避免长耗时脚本
Redis 是单线程的,长时间运行的脚本会阻塞其他命令。 -
使用
redis.pcall
替代redis.call
如果脚本可能出错,pcall
可以捕获错误而不中断执行。
6. 调试 Lua 脚本
- 日志输出:使用
redis.log(redis.LOG_WARNING, "debug info")
打印日志(可在 Redis 日志中查看)。 - 测试工具:在本地 Lua 环境中测试逻辑。
7. 总结
场景 | 适用 Lua 的原因 |
---|---|
复杂原子操作 | 如限流、分布式锁、条件更新 |
减少网络开销 | 合并多个 Redis 操作为一个脚本 |
高性能计算 | 避免频繁客户端-服务器通信 |
防缓存击穿/雪崩 | 通过 Lua 实现精细控制 |
最佳实践:
- 优先使用
EVALSHA
缓存脚本。 - 避免在 Lua 中执行耗时操作(如循环大列表)。
- 使用
redis.pcall
增强容错性。
通过 Lua 脚本,Redis 可以实现类似“存储过程”的功能,显著提升复杂场景下的性能和一致性。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)