redis 结合lua

举报
林欣 发表于 2025/06/30 16:30:10 2025/06/30
【摘要】 Redis 结合 Lua 脚本是一种强大的组合,能够以原子性、高性能的方式实现复杂逻辑。Lua 脚本在 Redis 中会被当作一个整体执行,期间不会中断,从而保证操作的原子性。以下是详细说明和实战示例: 1. 为什么在 Redis 中使用 Lua?原子性:整个脚本作为一个事务执行,不会被其他命令打断。减少网络开销:将多个操作合并为一个脚本,减少客户端与 Redis 之间的通信次数。灵活性:支...

Redis 结合 Lua 脚本是一种强大的组合,能够以原子性、高性能的方式实现复杂逻辑。Lua 脚本在 Redis 中会被当作一个整体执行,期间不会中断,从而保证操作的原子性。以下是详细说明和实战示例:


1. 为什么在 Redis 中使用 Lua?

  • 原子性:整个脚本作为一个事务执行,不会被其他命令打断。
  • 减少网络开销:将多个操作合并为一个脚本,减少客户端与 Redis 之间的通信次数。
  • 灵活性:支持条件判断、循环、复杂计算等逻辑。
  • 性能高:Redis 单线程执行 Lua 脚本,避免多线程竞争。

2. 基本用法

执行 Lua 脚本

使用 EVALEVALSHA(推荐)命令:

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. 获取分布式锁(避免多个客户端同时查DBif 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. 性能优化

  1. 使用 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
    
  2. 避免长耗时脚本
    Redis 是单线程的,长时间运行的脚本会阻塞其他命令。

  3. 使用 redis.pcall 替代 redis.call
    如果脚本可能出错,pcall 可以捕获错误而不中断执行。


6. 调试 Lua 脚本

  • 日志输出:使用 redis.log(redis.LOG_WARNING, "debug info") 打印日志(可在 Redis 日志中查看)。
  • 测试工具:在本地 Lua 环境中测试逻辑。

7. 总结

场景 适用 Lua 的原因
复杂原子操作 如限流、分布式锁、条件更新
减少网络开销 合并多个 Redis 操作为一个脚本
高性能计算 避免频繁客户端-服务器通信
防缓存击穿/雪崩 通过 Lua 实现精细控制

最佳实践

  1. 优先使用 EVALSHA 缓存脚本。
  2. 避免在 Lua 中执行耗时操作(如循环大列表)。
  3. 使用 redis.pcall 增强容错性。

通过 Lua 脚本,Redis 可以实现类似“存储过程”的功能,显著提升复杂场景下的性能和一致性。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。