使用 Lua 脚本在 Redis 中可以实现一些复杂的原子操作,比如分布式锁、计数器以及排行榜等。

1. 分布式锁

分布式锁是一种常用的机制,用于控制分布式系统中多个客户端对共享资源的访问。下面是一个简单的基于 Lua 脚本的分布式锁实现:

-- 设置锁的超时时间(秒)
local lockTimeout = tonumber(ARGV[1])
-- 当前请求者的唯一标识
local requestId = ARGV[2]

-- 尝试获取锁
local lockAcquired = redis.call('setnx', KEYS[1], requestId)

if lockAcquired == 1 then
    -- 如果获取锁成功,设置过期时间
    redis.call('expire', KEYS[1], lockTimeout)
else
    -- 检查锁是否已经过期
    local currentTime = redis.call('time')
    local ttl = redis.call('ttl', KEYS[1])
    if ttl == -2 then
        -- 锁不存在,尝试获取锁
        lockAcquired = redis.call('setnx', KEYS[1], requestId)
        if lockAcquired == 1 then
            redis.call('expire', KEYS[1], lockTimeout)
        end
    elseif (currentTime[1] + currentTime[2]/1000000) > lockTimeout then
        -- 锁已过期,尝试清除
        redis.call('del', KEYS[1])
        lockAcquired = redis.call('setnx', KEYS[1], requestId)
        if lockAcquired == 1 then
            redis.call('expire', KEYS[1], lockTimeout)
        end
    end
end

return lockAcquired

2. 原子计数器

原子计数器可以在并发情况下保证计数的准确性。以下是一个简单的计数器实现:

local countKey = KEYS[1]
local increment = tonumber(ARGV[1])

-- 增加计数器
redis.call('incrby', countKey, increment)

-- 返回当前计数值
return redis.call('get', countKey)

3. 排行榜

排行榜功能可以通过在 Redis 中使用有序集合(Sorted Set)来实现。下面是一个简单的 Lua 脚本,它允许添加用户分数,并返回排名前 N 的用户:

local leaderboardKey = KEYS[1]
local userId = ARGV[1]
local userScore = tonumber(ARGV[2])
local topN = tonumber(ARGV[3])

-- 更新用户的分数
redis.call('zadd', leaderboardKey, userScore, userId)

-- 获取排名前 N 的用户
local topUsers = redis.call('zrevrange', leaderboardKey, 0, topN-1, 'WITHSCORES')

return topUsers

在 Redis 客户端中执行 Lua 脚本

在 Python 的 Redis 客户端中执行上述脚本的一个例子如下:

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

# 分布式锁
lock_script = """
-- 脚本代码...
"""
lock_timeout = 300  # 5分钟
request_id = "unique_request_id"
lock_result = r.eval(lock_script, 1, 'my_lock', lock_timeout, request_id)

# 计数器
counter_script = """
-- 脚本代码...
"""
increment = 1
counter_result = r.eval(counter_script, 1, 'my_counter', increment)

# 排行榜
leaderboard_script = """
-- 脚本代码...
"""
top_n = 10
leaderboard_result = r.eval(leaderboard_script, 1, 'my_leaderboard', 'user1', 1234, top_n)