Redis里到底哪种锁更靠谱,能真保护数据不出错呢?
- 问答
- 2026-01-12 09:07:41
- 3
说到用Redis实现锁来保护数据不出错,这确实是一个实践中经常让人头疼的问题,你可能听说过一些方法,比如用简单的SETNX命令,或者更复杂一点的Redlock算法,但到底哪个更靠谱,能真正保证在高并发下数据万无一失呢?我们来直接看看几种主流的做法和它们的优缺点。
第一种:用SETNX命令实现简单锁
这是最原始、也是最容易想到的方法,SETNX的意思是“SET if Not eXists”,只有当这个键不存在时,才能设置成功,如果设置成功,就认为我拿到了锁;用完了之后,再用DEL命令把键删掉,相当于释放锁。
-
为什么它可能不靠谱?
- 死锁风险:这是最大的问题,如果某个客户端拿到锁之后,还没来得及释放(比如应用程序崩溃了、服务器宕机了、或者仅仅是发生了长时间的GC停顿),那么这个锁就会永远留在Redis里,其他所有客户端再也无法获得锁,系统就“死锁”了。
- 误删别人锁的风险:客户端A拿到了锁,但是它的业务操作执行时间超过了锁的预期时间,这时,锁可能因为超时被Redis自动删除了(或者被其他逻辑删除了),客户端B趁机拿到了锁,之后,客户端A执行完了,顺手就把客户端B的锁给删掉了,这会导致锁完全失效,多个客户端可能同时进入临界区。
为了解决死锁风险,人们通常会给锁加一个过期时间(expire),但早期的做法是先SETNX,再EXPIRE,这是两个操作,不是原子的,如果在这两个命令之间客户端崩溃了,锁还是变成了死锁,后来Redis提供了原子性命令,比如直接用
SET key value NX PX milliseconds,一步到位地设置锁和过期时间。
尽管加了过期时间,上述的第二个风险——“误删别人锁”的问题依然存在,为了解决这个问题,又引入了“随机值”的概念,即在设置锁时,value不是一个固定的值(locked”),而是一个唯一的随机字符串(比如UUID),客户端在删除锁的时候,要先判断当前锁的value是不是自己设置的那个随机值,只有是自己的,才能删除,这需要通过Lua脚本来保证判断和删除的原子性,避免在判断之后、删除之前锁过期并被其他客户端获取。
经过这些修补,SETNX加随机值和Lua脚本的方案,在大多数场景下已经比较可靠了,但它的可靠性严重依赖于一个假设:Redis是单点,如果这个Redis节点挂掉了呢?
第二种:Redlock算法
正是因为单点Redis锁存在单点故障的风险,Redis的作者Antirez提出了一个名为Redlock的分布式锁算法,这个算法的目的是在多个独立的Redis节点(通常是5个)上同时获取锁,来避免依赖单个Redis实例。

-
Redlock怎么工作?
- 客户端获取当前精确的时间戳。
- 它依次向N个(比如5个)独立的Redis实例尝试获取锁(使用和上面SETNX类似的方法,包含随机值和过期时间)。
- 只有当客户端从大多数(N/2 + 1,比如3个)节点上成功获取到锁,并且总耗时小于锁的过期时间,才认为获取锁成功。
- 如果获取锁失败,客户端会向所有Redis实例发送释放锁的请求。
-
为什么它听起来很靠谱,但争议巨大? Redlock的设计初衷是为了解决分布式环境下的可靠性问题,正是这个算法在社区引发了激烈的争论,特别是来自分布式系统专家Martin Kleppmann的挑战。
Martin Kleppmann在一篇名为《How to do distributed locking》的文章中指出了Redlock的问题,核心观点是:Redlock的安全性依赖于一个不切实际的时间假设,它假设所有节点的时钟是同步的,并且网络延迟和进程暂停(如GC)是可预测且短暂的,但在真实的分布式系统中,这些假设都可能被打破。
他举了一个经典的“毒丸”例子:

- 客户端A从3个节点拿到了锁。
- 由于长时间的GC暂停(比如STW垃圾回收),导致客户端A的进程“冻结”了很长时间,超过了锁的过期时间。
- Redis节点上的锁因为超时被自动释放了。
- 客户端B此时成功获取了锁,并开始修改受保护的数据。
- 客户端A从GC中“醒”来,它以为自己还持有锁(因为它不知道进程暂停了这么久),继续去修改数据,结果就是,客户端A和客户端B同时操作了数据,导致数据出错。
Martin认为,与其依赖一个脆弱的、对时间敏感的分布式锁,不如使用更成熟的方法,比如支持事务的数据库,或者带有版本号(fencing token,即栅栏令牌)的机制,Redis的作者Antirez对此进行了反驳,认为现实中出现长时间GC的概率很低,并且可以优化,Redlock在大多数场景下是可行的,这场争论没有绝对的赢家,但它深刻地揭示了分布式锁的复杂性。
到底哪种更靠谱?
回到最初的问题,Redis里哪种锁更靠谱?
-
对于绝大多数场景:使用单Redis实例的锁,即通过
SET resource_name random_value NX PX 30000命令,配合删除前用Lua脚本校验随机值的方案,是足够靠谱且简单的选择,它的优势在于简单、高效,你需要接受的风险是,如果这个Redis实例发生故障(比如主从切换时可能丢失数据),可能会导致锁的短暂不可用或重复获取,但对于很多业务场景,这种短暂的不一致是可以接受的,或者可以通过其他方式补偿。 -
对于要求极高一致性的关键场景:如果你认为单点Redis锁的风险不可接受,并且认为Redlock的争议性风险(对时钟和暂停的依赖)也不可接受,那么Redis可能就不是实现强一致性分布式锁的最佳工具,你应该考虑更基础的、本身就提供强一致性保证的系统,
- ZooKeeper:它通过Zab协议保证了强一致性,其临时顺序节点的特性非常适合实现分布式锁,并且能天然地处理客户端断开连接后锁自动释放的问题。
- etcd:类似ZooKeeper,通过Raft协议保证一致性,也提供了用于实现分布式锁的API原语。
没有绝对完美的锁,在Redis的范畴内,经过良好优化的单实例锁(随机值+Lua)是实践中的“性价比”之选;而Redlock则是一个更重、但争议也更大的方案,选用前需要仔细评估其假设是否符合你的环境,如果你的业务对正确性的要求是“100%绝对不能错”,那么可能需要跳出Redis,寻求ZooKeeper或etcd这类专门为协调而设计的系统。
(引用来源:Redis官方文档对SET命令NX、PX参数的说明;Redis作者Antirez发布的Redlock算法规范《Distributed locks with Redis》;Martin Kleppmann的批评文章《How to do distributed locking》)
本文由帖慧艳于2026-01-12发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://www.haoid.cn/wenda/79224.html
