DeepSeek Python 展示几个实用的 Redis Lua 脚本示例
·
我来为你展示几个实用的 Redis Lua 脚本示例:
- 基础 Lua 脚本示例
1.1 原子递增并设置过期时间
-- KEYS[1]: key名称
-- ARGV[1]: 初始值
-- ARGV[2]: 过期时间(秒)
local current = redis.call('GET', KEYS[1])
if current == false then
current = tonumber(ARGV[1])
redis.call('SET', KEYS[1], current)
else
current = tonumber(current) + 1
redis.call('SET', KEYS[1], current)
end
if tonumber(ARGV[2]) > 0 then
redis.call('EXPIRE', KEYS[1], ARGV[2])
end
return current
使用方式:EVAL “脚本内容” 1 counter_key 0 60
1.2 原子比较并设置
-- CAS (Compare And Set) 操作
-- KEYS[1]: key名称
-- ARGV[1]: 期望的旧值
-- ARGV[2]: 要设置的新值
local oldValue = redis.call('GET', KEYS[1])
if oldValue == ARGV[1] then
redis.call('SET', KEYS[1], ARGV[2])
return true
else
return false
end
- 分布式锁实现
2.1 获取锁
-- KEYS[1]: 锁key
-- ARGV[1]: 锁的值(随机唯一标识)
-- ARGV[2]: 过期时间(毫秒)
local result = redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2])
if result then
return 1
else
return 0
end
2.2 释放锁(安全版)
-- KEYS[1]: 锁key
-- ARGV[1]: 锁的值
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
- 限流器实现
3.1 滑动窗口限流
-- KEYS[1]: 限流key
-- ARGV[1]: 窗口大小(秒)
-- ARGV[2]: 最大请求数
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local currentTime = redis.call('TIME')
local now = tonumber(currentTime[1]) * 1000 + math.floor(tonumber(currentTime[2]) / 1000)
local clearBefore = now - (window * 1000)
redis.call('ZREMRANGEBYSCORE', key, 0, clearBefore)
local requestCount = redis.call('ZCARD', key)
if requestCount < limit then
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, window)
return 1 -- 允许通过
else
return 0 -- 限流
end
- 购物车操作
-- KEYS[1]: 购物车key
-- ARGV[1]: 操作类型(add/remove/update)
-- ARGV[2]: 商品ID
-- ARGV[3]: 数量
local cartKey = KEYS[1]
local operation = ARGV[1]
local itemId = ARGV[2]
local quantity = tonumber(ARGV[3])
if operation == 'add' then
local current = redis.call('HGET', cartKey, itemId)
if current then
quantity = quantity + tonumber(current)
end
redis.call('HSET', cartKey, itemId, quantity)
elseif operation == 'remove' then
redis.call('HDEL', cartKey, itemId)
elseif operation == 'update' then
if quantity <= 0 then
redis.call('HDEL', cartKey, itemId)
else
redis.call('HSET', cartKey, itemId, quantity)
end
elseif operation == 'clear' then
redis.call('DEL', cartKey)
end
-- 返回购物车总商品数
local items = redis.call('HGETALL', cartKey)
local totalItems = 0
for i = 2, #items, 2 do
totalItems = totalItems + tonumber(items[i])
end
return totalItems
- 投票系统
-- KEYS[1]: 文章投票key
-- KEYS[2]: 用户投票记录key
-- ARGV[1]: 用户ID
-- ARGV[2]: 文章ID
-- ARGV[3]: 投票类型(1: 赞成, 0: 反对)
local votedKey = KEYS[1]
local userKey = KEYS[2]
local userId = ARGV[1]
local articleId = ARGV[2]
local voteType = tonumber(ARGV[3])
-- 检查是否已投票
local userVoteKey = userKey .. ':' .. articleId .. ':' .. userId
local hasVoted = redis.call('GET', userVoteKey)
if hasVoted then
local oldVote = tonumber(hasVoted)
if oldVote == voteType then
return {0, 0} -- 重复投票
end
-- 撤销旧投票
if oldVote == 1 then
redis.call('HINCRBY', votedKey, articleId .. ':up', -1)
else
redis.call('HINCRBY', votedKey, articleId .. ':down', -1)
end
end
-- 记录新投票
redis.call('SET', userVoteKey, voteType, 'EX', 86400) -- 24小时
if voteType == 1 then
redis.call('HINCRBY', votedKey, articleId .. ':up', 1)
else
redis.call('HINCRBY', votedKey, articleId .. ':down', 1)
end
-- 返回当前票数
local upVotes = redis.call('HGET', votedKey, articleId .. ':up') or 0
local downVotes = redis.call('HGET', votedKey, articleId .. ':down') or 0
return {tonumber(upVotes), tonumber(downVotes)}
- 批量操作示例
-- 批量设置多个key并设置过期时间
local results = {}
for i = 1, #KEYS do
redis.call('SET', KEYS[i], ARGV[i])
redis.call('EXPIRE', KEYS[i], ARGV[#ARGV])
table.insert(results, 1)
end
return results
- 使用 Lua 脚本的最佳实践
7.1 脚本缓存
# 1. 加载脚本获取SHA
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
# 2. 使用EVALSHA执行
EVALSHA "获取到的SHA值" 1 mykey
7.2 错误处理示例
-- 带错误处理的脚本
local function safe_increment(key)
local value = redis.call('GET', key)
if not value then
value = 0
end
if type(tonumber(value)) ~= 'number' then
return redis.error_reply('Value is not a number')
end
local newValue = tonumber(value) + 1
redis.call('SET', key, newValue)
return newValue
end
return safe_increment(KEYS[1])
- 实际使用示例
import redis
import hashlib
class RedisLuaDemo:
def __init__(self):
self.redis = redis.Redis()
def rate_limit(self, user_id, action, limit=10, window=60):
"""限流器调用示例"""
script = """
local key = 'rate_limit:' .. KEYS[1] .. ':' .. KEYS[2]
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
else
return 1
end
"""
sha = hashlib.sha1(script.encode()).hexdigest()
try:
return self.redis.evalsha(sha, 2, user_id, action, limit, window)
except redis.exceptions.NoScriptError:
return self.redis.eval(script, 2, user_id, action, limit, window)
# 使用示例
demo = RedisLuaDemo()
allowed = demo.rate_limit('user123', 'login', 5, 60)
注意事项:
- 原子性:Lua 脚本在 Redis 中是原子执行的
- 参数传递:使用 KEYS 数组和 ARGV 数组
- 性能:避免在脚本中进行大量计算
- 调试:使用 redis.log() 输出日志
- 超时:复杂脚本可能导致 Redis 阻塞
- 复用:使用 SCRIPT LOAD 和 EVALSHA 提高性能
这些示例涵盖了 Redis Lua 脚本的常见使用场景,你可以根据实际需求进行修改和扩展。
更多推荐

所有评论(0)