分布式锁是Redis面试的第二高频题,也是线上事故的重灾区。面试官在看你会不会考虑极端情况——锁过期和业务耗时的竞争、GC停顿、客户端误删别人锁的bug——而不是只会"SETNX就完事了"。
最简正确姿势:SET key uniqueValue NX PX 30000。NX保证"不存在才设置"是原子的;PX设置过期时间防止客户端宕机导致锁永久占用;uniqueValue让只有持锁者能释放。
释放必须原子:用Lua脚本先判断value是否等于自己的uniqueValue,再DEL。不然GET和DEL之间锁可能已经过期被别人拿走,你再DEL就误删别人的锁。
讲过期时间的坑:锁TTL设30秒、业务跑了35秒,锁中途自动过期别人拿到锁,两个worker同时执行临界区。解决方案是看门狗——后台线程周期性续TTL,业务没结束就一直续。
加可重入:用线程ID为key计数(Redis hash或本地计数器),同一个线程可以重复获取自己的锁。Redisson的RLock就是用hash实现的,field是"threadId",value是重入次数。
讲Redlock:Antirez提出的多主HA方案,在N个独立Redis节点的多数上成功获取锁才算拿到。Martin Kleppmann曾指出它在GC停顿和时钟漂移下会失效,业界仍有争议。
Redis分布式锁的最小正确实现是SET key uniqueValue NX PX 30000。NX保证"只在key不存在时设置"是原子操作;PX设过期时间防止持锁客户端宕机后锁永久占用;uniqueValue一般是UUID,保证只有加锁的人才能解锁。释放必须原子——绝不能先GET再DEL,因为两步之间锁可能已经过期被别人拿走,你再DEL就把别人的锁干掉了。标准做法是用Lua脚本判断value相等再DEL,一次性在Redis里完成。更隐蔽的坑是"锁过期 vs 业务耗时"。TTL设30秒但业务跑了35秒,锁在第30秒自动过期被别的worker拿走,两个worker同时跑临界区,互斥被破坏。解决方案是看门狗——启动一个后台线程,业务没结束就周期性续TTL(比如每10秒续到30秒)。Redisson的RLock默认就是这套机制。可重入的实现是用线程ID做计数——Redisson用Redis hash,field是"threadId:lockName",value是重入计数,每次重入+1,释放-1,到0才真正删key。高可用场景Antirez提出Redlock,在N个独立Redis master的多数上加锁才算成功,容忍单点故障。但Martin Kleppmann写过著名的反对文章——客户端一次长GC停顿就可能让竞争者抢到锁而自己不知道,导致两个客户端同时认为自己持锁。严格正确要配合fencing token——加锁时返回一个单调递增的序号,存储层拒绝序号更小的写入。大多数业务场景不需要这么严格,单机Redis + 看门狗 + 原子释放已经够用。
释放锁一定要用Lua脚本,只DEL是典型事故bug。
看门狗机制和Redisson必须能讲出来,大部分候选人漏掉"TTL vs 业务耗时"这个点。
中高级岗要能讨论Redlock、fencing token和Kleppmann的反对意见。
如果忘了SET的参数顺序,即答侠可以实时提示NX PX的准确语法。
两条命令不是原子的。如果SETNX之后客户端宕机,EXPIRE没执行成功,这个key就永远不过期,成了死锁。必须用SET NX PX一条命令。
公平锁按申请顺序发锁(队列机制),非公平锁谁抢到算谁的。Redisson两种都支持。
不是。绝大多数业务单master加哨兵或集群就够了。Redlock会增加延迟和复杂度,且有已知失效场景。