Redis分布式锁原理与实践:从SETNX到Redlock,掌握高并发互斥核心

小编头像

小编

管理员

发布于:2026年04月28日

3 阅读 · 0 评论

北京时间2026年4月8日

在分布式系统的并发控制中,Redis分布式锁是高频考点,也是生产环境中最易踩坑的技术之一。很多开发者在项目中用过SETNX,也听过Redlock,但真要回答“Redis分布式锁到底怎么实现”“主从架构下锁为什么会失效”“Redlock解决了什么问题”时,往往答不完整甚至答错。本文将由浅入深,带你完整理解Redis分布式锁的核心原理、实现演进和高频面试考点。

一、痛点切入:为什么需要分布式锁?

在单机应用中,使用synchronizedReentrantLock就能轻松保证线程安全。但在分布式集群中,多个服务实例可能同时操作共享资源——比如秒杀场景下10台服务器同时扣减同一个商品的库存,单机锁根本无法跨实例生效。这就是分布式锁要解决的问题

来看一个典型的错误实现:

java
复制
下载
// 错误示范:分步执行SETNX + EXPIRE
jedis.setnx("lock:product:001", "1");  // 加锁
jedis.expire("lock:product:001", 30);  // 设置过期时间

致命缺陷:如果在执行完SETNX后Redis宕机或网络中断,EXPIRE命令未被执行,锁将永不释放,直接导致死锁。

更隐蔽的问题在于:即使解决了原子性问题,业务处理时间不可控——锁设了30秒过期,但业务逻辑跑了35秒,锁自动释放后其他线程趁虚而入,超卖就发生了

这些问题的根源,恰恰是理解Redis分布式锁价值的起点。

二、核心概念讲解:分布式锁

分布式锁的本质,是在一个所有节点都能访问的公共外部存储中设置一个排他性标记,确保同一时刻只有一个客户端能操作共享资源-6

用生活化的场景来理解:就像公司的唯一会议室,谁先拿到钥匙谁就能进去用,用完把钥匙还回去,其他人才能拿。区别在于,分布式锁还需要考虑“用钥匙的人突然失联了怎么办”——这就引出了过期时间的设计。

一个合格的分布式锁必须满足三个核心特性-33

特性含义类比
互斥性同一时刻只有一个客户端持有锁会议室钥匙一次只能给一个人
无死锁即使客户端崩溃,锁最终也能被释放失联的人不会一直占着钥匙
容错性部分节点故障时锁服务仍能运作备用钥匙柜能正常使用

三、关联概念讲解:SETNX + Lua脚本

SETNX(SET if Not eXists)是Redis实现分布式锁的基础命令——仅在Key不存在时设置值,天然适合互斥场景-21

但仅有SETNX远远不够。Redis官方推荐的标准加锁方式为:

redis
复制
下载
SET resource_name my_random_value NX PX 30000

这条命令原子地完成了三个动作:仅在Key不存在时设置(NX),同时设置30秒自动过期(PX 30000),并绑定一个唯一随机值作为锁的“签名”-13

为什么要唯一随机值? 看一个血案场景:

客户端A持有锁,但业务处理时间超过了锁的过期时间;锁自动释放,客户端B获取了锁;客户端A完成操作后直接执行DEL——此时删除的是客户端B的锁

解决这个问题,必须使用Lua脚本来保证“判断+删除”的原子性-13

lua
复制
下载
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

一句话理解SETNX与Lua脚本的关系:SETNX负责原子地加锁,Lua脚本负责安全地解锁——前者保证互斥,后者防止误删。

四、概念关系与区别总结

维度SETNXLua脚本
作用加锁(获取互斥访问权)解锁(安全释放)
原子性单命令天然原子脚本内所有操作原子执行
核心价值保证互斥性保证解锁时的身份校验安全

一句话概括:SETNX负责“锁住门”,Lua脚本负责“用正确钥匙开门”。

五、代码示例演示

完整加锁+解锁实现(Python)

python
复制
下载
import redis
import uuid

r = redis.StrictRedis()
lock_key = "lock:product:001"
client_id = str(uuid.uuid4())

 加锁:原子操作,过期时间30秒
acquired = r.set(lock_key, client_id, nx=True, ex=30)

if acquired:
    try:
         临界区代码(如扣减库存)
        pass
    finally:
         释放锁:Lua脚本校验身份
        lua_script = """
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """
        r.eval(lua_script, 1, lock_key, client_id)

执行流程解析

  1. 生成唯一标识client_id(UUID)

  2. 调用SET key value NX EX 30,原子完成加锁+设过期

  3. 若返回True,说明加锁成功,进入临界区

  4. 释放时执行Lua脚本,仅当当前客户端是锁的持有者时才执行DEL

对比改进效果:错误实现(分步SETNX+EXPIRE)存在死锁风险;正确实现(原子SET+Lua释放)同时解决了死锁误删两大核心问题。

六、底层原理/技术支撑点

Redis分布式锁的底层依赖于以下关键技术:

  • Redis单线程命令处理模型:每个命令天然原子执行,是分布式锁互斥性的基础

  • Lua脚本机制:在Redis服务端原子执行多条命令,避免竞态条件-

  • Key过期机制(TTL):自动释放锁,防止客户端崩溃导致的死锁

  • WatchDog机制(以Redisson为例):后台线程定期检查业务是否完成,动态延长锁的过期时间。默认每隔锁有效期的1/3时间(如锁30秒则每隔10秒)检查一次,未完成则重置为30秒-33

注意:WatchDog仅在调用lock()且未显式指定leaseTime时启动。如果写成lock.lock(10, TimeUnit.SECONDS),看门狗将不会启动-6

七、进阶:Redlock算法与主从架构的困境

为什么需要Redlock?

Redis主从架构存在一个致命缺陷:异步复制导致锁可能丢失

客户端A在主节点获取锁,但写入尚未同步到从节点时主节点宕机;从节点被提升为新主节点;客户端B在新主节点成功获取同一资源的锁——两个客户端同时持有了锁,互斥性被破坏-13

从CAP理论看,Redis主从架构遵循AP(高可用+分区容忍),牺牲了强一致性,这正是主从切换时锁失效的根源-11

Redlock算法核心思想

Redis官方提出的Redlock算法,通过多个独立的Redis实例来解决单点失效问题:只有在超过半数的Redis节点上成功获取锁,才算加锁成功-11

标准加锁步骤-51

  1. 获取当前时间戳

  2. 依次向N个独立Redis节点(推荐N=5)尝试加锁

  3. 每个节点使用SET key value NX PX原子加锁

  4. 若超过半数(N/2+1)节点加锁成功,且总耗时 < 锁过期时间,则加锁成功

  5. 否则依次释放所有节点上的锁

go
复制
下载
// Redlock核心逻辑伪代码(Go)
quorum := len(clients)/2 + 1  // 5节点 → quorum=3
for _, client := range clients {
    if client.SetNX(ctx, key, token, ttl).Val() {
        success++
    }
    if time.Since(start) > ttl/2 {
        break  // 超出安全窗口则放弃
    }
}
return success >= quorum

使用建议:大多数业务场景下不需要Redlock——它要求5个独立Redis节点,多数成功才算拿锁,代价是延迟高、实现复杂。只有在金融、支付等对数据一致性要求极高的场景才值得引入-

八、高频面试题与参考答案

Q1:Redis分布式锁的实现步骤?

参考答案

  1. 使用SET key value NX EX seconds原子完成加锁+设过期

  2. value必须是全局唯一标识(如UUID)

  3. 释放锁时使用Lua脚本校验value后删除

  4. 业务耗时不确定时,引入看门狗自动续期机制

Q2:为什么不能用SETNX+EXPIRE两步实现?

参考答案:两步操作非原子,存在死锁风险——若SETNX后Redis宕机或网络中断,EXPIRE未执行,锁将永不释放。

Q3:Redlock解决了什么问题?有哪些争议?

参考答案:Redlock解决Redis主从架构下异步复制导致锁丢失的问题,通过多个独立Redis节点和多数派投票保障互斥性。争议点在于:Redlock依赖时钟同步假设,网络分区或时钟漂移时仍可能失效,且实现复杂度远高于单实例方案,多数业务场景不需要。

Q4:Redisson相比手写SETNX有哪些优势?

参考答案

  • 可重入:同一线程可重复获取同一锁,使用Hash结构存储重入次数

  • 自动续期:看门狗机制自动延长锁过期时间

  • 原子性保障:所有操作通过Lua脚本实现

  • 工程完备:内置锁语义、公平锁、红锁等多种模式

九、结尾总结

阶段核心技术解决的问题
基础SET NX PX + Lua原子加锁、防死锁、防误删
进阶Redisson + WatchDog自动续期、可重入
高可用Redlock算法主从切换时锁丢失

重点回顾

  • 加锁必须原子(SET NX PX),释放必须校验身份(Lua)

  • 业务处理时间 > 锁过期时间 → 超卖风险 → 需要续期机制

  • 主从异步复制 → 锁可能丢失 → 高一致性场景考虑Redlock

易错点:锁粒度不够精细、直接用固定字符串当锁key、释放锁时忘记校验value直接DEL

下篇预告:Redisson源码深度剖析——看门狗机制的Lua实现与分布式锁的集群适配。


参考资料:Redis官方文档、JavaGuide分布式锁总结、阿里云开发者社区、CSDN技术博客等。

标签:

相关阅读