我来为你展示几个实用的 Redis Lua 脚本示例:

  1. 基础 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
  1. 分布式锁实现

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
  1. 限流器实现

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
  1. 购物车操作
-- 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
  1. 投票系统
-- 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)}
  1. 批量操作示例
-- 批量设置多个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
  1. 使用 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])
  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)

注意事项:

  1. 原子性:Lua 脚本在 Redis 中是原子执行的
  2. 参数传递:使用 KEYS 数组和 ARGV 数组
  3. 性能:避免在脚本中进行大量计算
  4. 调试:使用 redis.log() 输出日志
  5. 超时:复杂脚本可能导致 Redis 阻塞
  6. 复用:使用 SCRIPT LOAD 和 EVALSHA 提高性能

这些示例涵盖了 Redis Lua 脚本的常见使用场景,你可以根据实际需求进行修改和扩展。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐