通过lua实现redis 分布式锁的项目实践

 更新时间:2026年02月04日 10:28:10   作者:哈里谢顿  
在Redis中使用Lua脚本实现分布式锁 是一种高效、原子、安全的方式,本文就来介绍一下通过lua实现redis分布式锁,具有一定的参考价值,感兴趣的可以了解一下

在 Redis 中使用 Lua 脚本实现分布式锁 是一种高效、原子、安全的方式。由于 Lua 脚本在 Redis 中是单线程、原子执行的,可以避免多个客户端在加锁/解锁时出现竞态条件(Race Condition)。

下面将为你提供一个 完整、生产可用的 Redis 分布式锁 Lua 实现方案,包含:

  • 加锁(lock)
  • 解锁(unlock)
  • 自动续期(可选)
  • 防止误删他人锁
  • 支持可重入(可选)

✅ 一、核心设计原则

  1. 锁的唯一标识:每个客户端使用唯一的 value(如 UUID)作为锁标识,防止 A 客户端释放 B 客户端的锁。
  2. 原子性操作:加锁和解锁必须通过 Lua 脚本保证原子性。
  3. 自动过期:设置 TTL(如 30 秒),防止死锁。
  4. 可重入支持(可选) :同一个客户端可多次加锁(需记录重入次数)。

本文先实现 基础版(不可重入) ,再给出 可重入增强版

✅ 二、基础版:不可重入分布式锁

🔐 1. 加锁脚本(lock.lua)

-- KEYS[1] = 锁的 key(如 "my_lock")
-- ARGV[1] = 锁的 value(唯一标识,如 UUID)
-- ARGV[2] = 过期时间(毫秒,如 "30000")

local key = KEYS[1]
local value = ARGV[1]
local ttl = tonumber(ARGV[2])

-- 尝试加锁:只有当 key 不存在时才设置成功
if redis.call("SET", key, value, "NX", "PX", ttl) then
    return 1  -- 加锁成功
else
    return 0  -- 加锁失败(已被占用)
end

✅ SET key value NX PX ttl 是 Redis 2.6.12+ 的原子命令:

  • NX:仅当 key 不存在时设置
  • PX:设置过期时间(毫秒)

🔓 2. 解锁脚本(unlock.lua)

-- KEYS[1] = 锁的 key
-- ARGV[1] = 锁的 value(用于验证是否是自己的锁)

local key = KEYS[1]
local value = ARGV[1]

-- 获取当前锁的 value
local current_value = redis.call("GET", key)

-- 如果存在且等于自己的 value,则删除
if current值 == value then
    redis.call("DEL", key)
    return 1  -- 解锁成功
else
    return 0  -- 不是自己的锁,拒绝删除
end

⚠️ 注意:不能直接 DEL,否则可能误删别人的锁!

✅ 三、Python 示例(使用 redis-py)

import redis
import uuid
import time

# 初始化 Redis 连接
r = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)

# 加载 Lua 脚本(推荐使用 script_load + evalsha 提高性能)
lock_script = r.register_script("""
if redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) then
    return 1
else
    return 0
end
""")

unlock_script = r.register_script("""
if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("DEL", KEYS[1])
else
    return 0
end
""")

def acquire_lock(lock_name, acquire_timeout=10, lock_timeout=30):
    """获取分布式锁"""
    identifier = str(uuid.uuid4())
    lock_timeout_ms = int(lock_timeout * 1000)
    end_time = time.time() + acquire_timeout

    while time.time() < end_time:
        # 执行 Lua 脚本尝试加锁
        if lock_script(keys=[lock_name], args=[identifier, lock_timeout_ms]):
            return identifier  # 返回锁标识
        time.sleep(0.01)  # 短暂等待后重试

    return False

def release_lock(lock_name, identifier):
    """释放分布式锁"""
    unlock_script(keys=[lock_name], args=[identifier])

# ===== 使用示例 =====
lock_key = "order:123:lock"

# 尝试获取锁
lock_id = acquire_lock(lock_key, acquire_timeout=5, lock_timeout=30)
if lock_id:
    try:
        print("✅ 获取锁成功,执行业务逻辑...")
        time.sleep(5)  # 模拟业务处理
    finally:
        # 释放锁
        release_lock(lock_key, lock_id)
        print("🔓 锁已释放")
else:
    print("❌ 获取锁超时")

✅ 四、增强版:支持可重入锁(Reentrant Lock)

适用于同一线程/协程需要多次进入临界区的场景。

设计思路:

  • 使用 Hash 结构 存储锁:

    • field = 客户端 ID(UUID)
    • value = 重入次数(counter)
  • 设置整体 TTL

🔐 可重入加锁脚本(reentrant_lock.lua)

-- KEYS[1] = 锁 key
-- ARGV[1] = 客户端 ID
-- ARGV[2] = 锁过期时间(毫秒)

local key = KEYS[1]
local client_id = ARGV[1]
local ttl = tonumber(ARGV[2])

-- 检查是否已持有锁
local current_client = redis.call("HGET", key, client_id)

if current_client then
    -- 已持有:重入次数 +1,刷新 TTL
    redis.call("HINCRBY", key, client_id, 1)
    redis.call("PEXPIRE", key, ttl)
    return 1
else
    -- 未持有:尝试获取锁
    if redis.call("HLEN", key) == 0 then
        -- 锁空闲,创建
        redis.call("HMSET", key, client_id, 1)
        redis.call("PEXPIRE", key, ttl)
        return 1
    else
        -- 锁被他人持有
        return 0
    end
end

🔓 可重入解锁脚本(reentrant_unlock.lua)

-- KEYS[1] = 锁 key
-- ARGV[1] = 客户端 ID

local key = KEYS[1]
local client_id = ARGV[1]

-- 检查是否持有锁
local counter = redis.call("HGET", key, client_id)

if not counter then
    return 0  -- 未持有锁
end

counter = tonumber(counter)

if counter > 1 then
    -- 重入次数 >1,减1
    redis.call("HINCRBY", key, client_id, -1)
    return 1
else
    -- 最后一次,删除整个 key
    redis.call("DEL", key)
    return 1
end

💡 使用方式与基础版类似,只需替换 Lua 脚本。

✅ 五、注意事项与最佳实践

问题解决方案
锁过期但业务未完成使用“看门狗”(Watchdog)线程自动续期(如 Redisson 的机制)
主从切换导致锁丢失使用 Redlock 算法(多 Redis 实例),但争议较大;更推荐强一致存储(如 etcd)
Lua 脚本调试困难在开发环境打印日志(redis.log(redis.LOG_WARNING, "msg"))
性能优化使用 SCRIPT LOAD + EVALSHA 避免每次传输脚本

📌 重要提醒:
Redis 分布式锁 不保证绝对安全(尤其在主从异步复制场景)。
对一致性要求极高的场景(如金融),建议使用 etcd / ZooKeeper。

✅ 六、总结

功能基础版可重入版
原子加锁
防止误删
自动过期
同客户端多次加锁
复杂度

推荐:

  • 普通场景 → 用 基础版
  • 需要递归调用/嵌套锁 → 用 可重入版

通过以上 Lua 脚本 + 客户端封装,你可以在 Redis 中实现一个高效、安全、可靠的分布式锁系统。

到此这篇关于通过lua实现redis 分布式锁的项目实践的文章就介绍到这了,更多相关lua redis 分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mac下设置redis开机启动方法步骤

    mac下设置redis开机启动方法步骤

    这篇文章主要介绍了mac下设置redis开机启动,本文详细的给出了操作步骤,需要的朋友可以参考下
    2015-07-07
  • Redis中Redlock算法的具体使用

    Redis中Redlock算法的具体使用

    Redlock是Redis提供的分布式锁算法,通过多个独立Redis实例实现高可用的分布式锁,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-10-10
  • Redis 的查询很快的原因解析及Redis 如何保证查询的高效

    Redis 的查询很快的原因解析及Redis 如何保证查询的高效

    由于redis是内存数据库,归功于它的数据结构所以查询效率非常高,今天通过本文给大家介绍下Redis 的查询很快的原因解析及Redis 如何保证查询的高效,感兴趣的朋友一起看看吧
    2022-03-03
  • 浅谈Redis哨兵模式高可用解决方案

    浅谈Redis哨兵模式高可用解决方案

    Redis高可用有两种模式:哨兵模式和集群模式,本文基于哨兵模式搭建一主两从三哨兵Redis高可用服务,感兴趣的可以了解一下
    2022-03-03
  • redis++的编译 安装 使用方案

    redis++的编译 安装 使用方案

    这篇文章主要介绍了redis++的编译 安装 使用方案的相关资料,需要的朋友可以参考下
    2023-03-03
  • Redis不使用 keys 命令获取键值信息的方法

    Redis不使用 keys 命令获取键值信息的方法

    这篇文章主要介绍了Redis 不使用 keys 命令获取键值信息的相关知识,非常不错,具有一定的参考借鉴价值,需要的朋友参考下吧
    2018-08-08
  • 解决Redis启动警告问题

    解决Redis启动警告问题

    这篇文章介绍了解决Redis启动警告问题的方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-02-02
  • 批量导入txt数据到的redis过程

    批量导入txt数据到的redis过程

    用户通过将Redis命令逐行写入txt文件,利用管道模式运行客户端,成功执行批量删除以"Product*"匹配的Key操作,提高了数据清理效率
    2025-08-08
  • Redis缓存与数据库一致性的完整指南

    Redis缓存与数据库一致性的完整指南

    某金融平台因缓存数据不一致导致用户余额错乱,损失千万!文中将用银行对账比喻+实战代码,揭秘6大解决方案,让你的数据毫秒级同步,所以本文给大家详细介绍了Redis缓存与数据库一致性的完整指南,需要的朋友可以参考下
    2025-09-09
  • 浅谈Redis在秒杀场景的作用

    浅谈Redis在秒杀场景的作用

    本文主要介绍了浅谈Redis在秒杀场景的作用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01

最新评论