Redis分布式锁通过SETNXEX原子操作加锁、唯一value标识及Lua脚本安全解锁实现。需处理锁过期续期(看门狗)和主从切换丢锁问题,生产推荐Redisson框架。
先从一个经典场景说起——双11秒杀,10000个人抢100个商品。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
单机锁为什么不行?因为秒杀系统通常有10台服务器,每台都有自己的内存,锁只能锁住自己,管不了别的机器。这时候,需要一个所有服务器都能访问的共享锁,Redis分布式锁就是为此而生的。
// 新手最容易写的错误代码
public boolean lock(String key) {
String result = jedis.setnx(key, "1"); // 尝试加锁
return result == 1; // 1表示加锁成功
}
public void unlock(String key) {
jedis.del(key); // 删除锁
}
这个写法问题很大。
// 这个也有问题
public boolean lock(String key, int seconds) {
// 两步操作:1.加锁 2.设置过期时间
Long result = jedis.setnx(key, "1");
if (result == 1) {
jedis.expire(key, seconds); // 设置过期
return true;
}
return false;
}
问题在于,setnx和expire是两步操作,不是原子的。如果setnx成功,但expire之前程序崩溃了,锁就变成永久的了——这恰恰是刚才提到的死锁问题。
// 正确的加锁(一步完成)
public boolean lock(String key, String value, int seconds) {
// 一条命令完成:加锁+设置过期时间
String result = jedis.set(key, value, "NX", "EX", seconds);
return "OK".equals(result);
}
// 安全的解锁
public void unlock(String key, String value) {
// 用Lua脚本保证原子操作:检查值再删除
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
jedis.eval(script, 1, key, value);
}
核心要点有三个:
SET key value NX EX seconds,加锁和设置过期时间是原子操作。// 错误:大家value都一样
jedis.set("lock", "1", "NX", "EX", 10);
// 正确:每人一个唯一标识
String myId = UUID.randomUUID().toString();
jedis.set("lock", myId, "NX", "EX", 10);
来看一个实际场景:
线程A:获得锁,value="A123",超时10秒
线程A:执行了15秒(锁在第10秒已过期)
线程B:获得锁,value="B456"
线程A:终于执行完,要删除锁 → 删了线程B的锁!
用唯一标识就能避免这种情况:删除前检查value是否还是自己的,不是就别动。
方案1:设置合理的过期时间。提前评估业务执行时间,把过期时间设得足够长。
// 评估业务时间,设置更长过期
jedis.set("lock", uuid, "NX", "EX", 30); // 设置30秒
方案2:自动续期(看门狗模式)。启动一个线程定期检查,业务还没执行完就延长锁的过期时间。
// 启动一个线程,定期续期
new Thread(() -> {
while (业务没执行完) {
Thread.sleep(8000); // 8秒续一次
// 如果是自己的锁,就延长过期时间
jedis.expire("lock", 10);
}
}).start();
会!这是Redis分布式锁最大的隐患。
场景还原:
1. 线程A在主节点获得锁
2. 主节点宕机(锁数据还没同步到从节点)
3. 从节点变成新主节点
4. 线程B在新主节点获得“相同”的锁
结果:A和B同时持有了锁!
解决方案有几个:
// 使用Redisson框架(最省心)
RedissonClient redisson = Redisson.create();
RLock lock = redisson.getLock("myLock");
try {
lock.lock(); // 加锁(自动续期)
// 执行业务...
} finally {
lock.unlock(); // 解锁
}
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Redis单节点 | 简单、快 | 主从切换丢锁 | 测试环境、不重要的锁 |
| RedLock | 相对可靠 | 实现复杂、性能差 | 重要的业务锁 |
| Redisson | 功能全、自动续期 | 依赖框架 | 推荐的生产方案 |
| ZooKeeper | 最可靠 | 性能较差 | 强一致性的场景 |
Q1: “说一下Redis分布式锁的实现原理”
答:用SET命令的NX和EX参数,NX保证只有一个能设置成功,EX设置过期时间防止死锁。删除时用Lua脚本原子操作,避免删别人锁。
Q2: “Redis锁和ZooKeeper锁的区别?”
答:Redis是AP系统,性能好但可能丢锁;ZooKeeper是CP系统,可靠但性能差。选择取决于需求:要高性能用Redis,要可靠性用ZooKeeper。
Q3: “怎么实现可重入锁?”
答:在value里记录线程ID和重入次数。加锁时如果是同一线程,计数+1;解锁时计数-1,计数为0才真正删除锁。
SET key uuid NX EX seconds你的分布式锁:
满足这5条,面试官就难不倒你了。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述