Redis lua set使用场景举例

举报
林欣 发表于 2025/06/30 16:43:56 2025/06/30
【摘要】 在 Redis 的 Lua 脚本中,SET 类型(即 Redis 的字符串类型)虽然看似简单,但结合 Lua 的逻辑控制能力后,可以高效实现许多复杂场景。以下是 SET 类型在 Lua 脚本中的典型使用场景及代码示例,涵盖原子操作、条件判断、分布式锁等核心需求。 1. 原子性计数器场景:实现一个原子性的计数器,支持递增、递减、重置等操作。优势:Lua 脚本保证整个计数过程的原子性,避免竞态条...

在 Redis 的 Lua 脚本中,SET 类型(即 Redis 的字符串类型)虽然看似简单,但结合 Lua 的逻辑控制能力后,可以高效实现许多复杂场景。以下是 SET 类型在 Lua 脚本中的典型使用场景及代码示例,涵盖原子操作、条件判断、分布式锁等核心需求。


1. 原子性计数器

场景:实现一个原子性的计数器,支持递增、递减、重置等操作。
优势:Lua 脚本保证整个计数过程的原子性,避免竞态条件。

示例:带阈值检查的计数器

-- KEYS[1]: 计数器键名(如 "page:views"-- ARGV[1]: 递增步长(如 1-- ARGV[2]: 最大阈值(如 1000)
local key = KEYS[1]
local increment = tonumber(ARGV[1])
local max_limit = tonumber(ARGV[2])

local current = tonumber(redis.call('GET', key) or '0')
local new_value = current + increment

if new_value > max_limit then
    return 0  -- 超过阈值,返回失败
else
    redis.call('SET', key, new_value)
    return new_value  -- 返回新值
end

调用方式

127.0.0.1:6379> EVAL "..." 1 page:views 1 1000

2. 分布式锁(安全获取与释放)

场景:在分布式系统中实现互斥锁,确保同一时间只有一个客户端能操作共享资源。
关键点

  • 使用 SET NX PX 原子性获取锁。
  • 通过 Lua 脚本确保“检查锁归属”和“释放锁”的原子性。

(1) 获取锁

-- KEYS[1]: 锁键名(如 "order:lock"-- ARGV[1]: 锁的唯一标识(如客户端 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 "client-123" 30000

(2) 释放锁(安全删除)

-- KEYS[1]: 锁键名
-- 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 "client-123"

3. 缓存防击穿(热点数据保护)

场景:当缓存失效时,防止大量请求直接穿透到数据库。
策略

  1. 检查缓存是否存在。
  2. 如果不存在,先获取分布式锁,再查询数据库并更新缓存。
  3. 其他请求在锁未释放时返回默认值或等待。

示例脚本

-- KEYS[1]: 数据键名(如 "product:1001"-- ARGV[1]: 锁键名(如 "product:1001:lock"-- ARGV[2]: 锁的过期时间(秒)
local data_key = KEYS[1]
local lock_key = ARGV[1]
local lock_ttl = tonumber(ARGV[2])

-- 1. 尝试从缓存获取数据
local data = redis.call('GET', data_key)
if data then
    return data  -- 缓存命中
end

-- 2. 获取分布式锁
if redis.call('SET', lock_key, '1', 'NX', 'EX', lock_ttl) then
    -- 模拟查询数据库(实际应替换为真实逻辑)
    local db_data = "db_result_for_" .. data_key
    redis.call('SET', data_key, db_data, 'EX', 60)  -- 写入缓存,有效期60redis.call('DEL', lock_key)  -- 释放锁
    return db_data
else
    -- 未获取锁,返回空或等待(根据业务需求)
    return nil
end

调用方式

127.0.0.1:6379> EVAL "..." 1 product:1001 product:1001:lock 5

4. 条件更新(CAS 操作)

场景:实现“比较并设置”(Compare-And-Swap)操作,确保数据一致性。
示例:只有当当前值等于预期值时,才更新为新值。

-- KEYS[1]: 目标键名
-- ARGV[1]: 预期值
-- ARGV[2]: 新值
local key = KEYS[1]
local expected = ARGV[1]
local new_value = ARGV[2]

if redis.call('GET', key) == expected then
    redis.call('SET', key, new_value)
    return 1  -- 更新成功
else
    return 0  -- 条件不满足,未更新
end

调用方式

127.0.0.1:6379> SET version "1.0"
OK
127.0.0.1:6379> EVAL "..." 1 version "1.0" "2.0"  # 成功更新
(integer) 1
127.0.0.1:6379> EVAL "..." 1 version "1.0" "3.0"  # 条件不满足
(integer) 0

5. 限流器(固定窗口算法)

场景:限制某个键在指定时间窗口内的操作次数。
逻辑

  1. 每次操作时递增计数器。
  2. 如果计数器超过阈值,则拒绝操作。
  3. 计数器过期后自动重置。
-- KEYS[1]: 限流键名(如 "api:rate_limit:user123"-- ARGV[1]: 阈值(如 100-- ARGV[2]: 时间窗口(秒)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])

local current = tonumber(redis.call('GET', key) or '0')
if current >= limit then
    return 0  -- 超过限流阈值
end

redis.call('INCR', key)
if current == 0 then
    redis.call('EXPIRE', key, window)  -- 首次设置过期时间
end
return 1  -- 允许操作

调用方式

127.0.0.1:6379> EVAL "..." 1 api:rate_limit:user123 100 60

6. 配置热更新

场景:动态更新系统配置,无需重启服务。
示例:修改全局开关或参数。

-- KEYS[1]: 配置键名(如 "system:config:feature_flag"-- ARGV[1]: 新配置值(如 "enabled")
local key = KEYS[1]
local new_value = ARGV[1]

redis.call('SET', key, new_value)
return redis.call('GET', key)  -- 返回新值

调用方式

127.0.0.1:6379> EVAL "..." 1 system:config:feature_flag "enabled"

7. 总结:SET 类型在 Lua 中的核心优势

场景 优势
原子计数器 避免竞态条件,无需外部同步机制。
分布式锁 通过 SET NX PX + Lua 脚本实现安全可靠的锁机制。
缓存防击穿 结合锁和缓存,防止数据库被突发流量打垮。
条件更新 实现类似数据库的 CAS 操作,保证数据一致性。
限流器 简单高效的固定窗口限流算法。
配置热更新 动态修改配置,实时生效。

最佳实践

  1. 优先使用 EVALSHA:缓存脚本 SHA1,减少网络传输。
  2. 错误处理:用 redis.pcall 捕获异常,避免脚本中断。
  3. 键命名规范:使用 : 分隔命名空间(如 service:config:key)。
  4. 避免长脚本:Redis 是单线程的,长时间运行的脚本会阻塞其他命令。

通过 Lua 脚本与 SET 类型的结合,Redis 可以轻松应对各种高并发、一致性要求严格的场景。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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