Redis其实没真锁,解密那些所谓无锁操作背后的秘密和误区
- 问答
- 2026-01-03 03:55:04
- 2
(根据微信公众号“码农翻身”文章《Redis的锁,你真的用对了吗?》及其他技术社区讨论综合整理)
很多人一提到Redis,就会想到用它来做“分布式锁”,网上随便一搜,也能找到大量使用Redis的SETNX命令实现锁的教程,这给很多开发者造成了一个印象:Redis自带了一个叫“锁”的东西,就像Java里的synchronized关键字一样,拿不到锁的线程会乖乖排队等待。
但这里其实存在一个巨大的认知误区:Redis本身并不提供现成的、开箱即用的“锁”功能。 我们通常所说的“Redis分布式锁”,其实是聪明的程序员们利用Redis的几个基础命令,像搭积木一样,手动构建出来的一套模拟锁的机制,这套机制看似简单,背后却暗藏玄机,一不小心就会掉进坑里。
第一个秘密:所谓“加锁”,只是一条SET命令
当我们说“加锁”时,我们做了什么?最核心的命令就是SET key value NX PX 30000,这条命令的意思是:如果这个key不存在(NX选项),我就把它设置成这个value,并且给它30秒的过期时间(PX选项),如果设置成功了,Redis返回OK,我们就认为自己“拿到锁”了,如果key已经存在(说明锁被别人拿走了),Redis返回nil,我们就“加锁失败”。
看明白了吗?Redis服务器本身根本不知道你在做什么“加锁”操作。 它只是在忠实地执行一条设置键值对的指令,并遵循了“不存在才设置”和“自动过期”的规则,所谓的“锁”,只是我们赋予这套操作流程的一个业务含义,Redis内心可能在想:“我只是个缓存数据库,你们人类真复杂。”

第二个误区:你以为安全了,其实漏洞百出
正因为这个“锁”是模拟出来的,所以它的坚固程度完全取决于我们搭建得是否完美,以下是几个经典的坑:
-
坑一:锁忘了释放,变成死锁。 这是最致命的错误,如果程序A拿到锁之后,在处理业务逻辑时突然崩溃了,或者发生了死循环,导致没有机会执行删除key(解锁)的命令,这个key就会永远留在Redis里,其他所有进程再来加锁时,都会因为key已存在而失败,整个系统就被一把“死锁”卡住了,为了解决这个问题,才引入了给锁设置过期时间(PX参数)的方法,但这就引出了第二个坑。
-
坑二:锁被误释放。 假设程序A拿到锁,设置了30秒过期,但它的业务逻辑需要执行40秒,在它执行到第35秒时,Redis因为过期时间已到,自动把锁删除了,程序B成功加锁,5秒后,程序A终于执行完了,它依然会习惯性地执行删除key的命令,结果就是:程序A释放了程序B的锁! 紧接着程序C也能加锁了,于是系统里同时出现了B和C在操作共享资源,数据混乱就此发生。

-
坑三:解决误释放,带来新复杂度。 为了防止自己的锁被别人释放,我们想了个办法:在加锁时,value不再设置成简单的1或"lock",而是设置成一个全局唯一的随机值(比如UUID),在释放锁的时候,先检查一下这个key对应的value是不是自己当初设置的那个随机值,如果是,才删除它,这需要用到Lua脚本保证原子性,看,为了解决一个坑,我们的“模拟锁”机制又复杂了一层。
第三个秘密:主从切换下的更大风险
即使你完美避开了上述所有坑,在Redis采用主从模式部署时,还有一个更隐蔽的“大杀器”,假设程序A在主节点上成功加锁,在锁数据还未同步到从节点时,主节点突然宕机了,哨兵机制会选举一个从节点成为新的主节点,但这个新的主节点上并没有程序A的锁数据!程序B来向新主节点申请加锁,会成功拿到锁,程序A和程序B都认为自己持有锁,同时操作资源, again,数据一致性被彻底破坏。
Redis的“锁”是脆弱的工艺品 Redis其实没有“真锁”,它提供的只是砖块(基础命令),我们用它搭建了一个名为“分布式锁”的沙堡,这个沙堡在风平浪静(单机Redis、网络稳定、业务执行时间可控)时看起来很坚固,但一旦遇到异常情况(进程崩溃、网络延迟、主从切换),就可能瞬间垮塌。
对于非核心的、允许偶尔出错的场景(比如防止缓存击穿),用Redis模拟锁是简单有效的,但对于金融交易等要求强一致性的核心场景,Redis分布式锁的风险就太高了,在这些场景下,更稳妥的做法是使用专门为分布式协调而设计的工具,比如ZooKeeper或etcd,它们提供了真正的原生的分布式锁服务,虽然性能可能不如Redis,但安全性要高得多,理解Redis锁的“模拟”本质和其背后的陷阱,是我们正确使用它的第一步。
本文由酒紫萱于2026-01-03发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/73476.html
