一、为什么需要分布式锁?
在分布式系统中,多台服务器并发访问共享资源(如库存、订单、优惠券)时,必须使用“锁”机制来保证数据一致性和正确性。但 Java 中的 synchronized
或 ReentrantLock
是线程级别的,只能在单机中使用。
所以就需要跨节点可见、跨进程有效的锁:分布式锁。
二、Redis 实现分布式锁的基本方案
分布式锁是一种用于控制多个不同进程在分布式系统中访问共享资源的锁机制。它确保在同一时刻,只有一个节点可以对资源进行访问,从而避免并发问题。
✅ 最简单实现方式(SETNX)
Redis 提供 SETNX
命令(SET if Not eXists)可以天然实现“加锁”的语义:
SET lock_key lock_value NX PX 30000
含义:
lock_key
:锁的 key(锁名),比如order:lock
lock_value
:请求标识(通常是 UUID,防止误删)NX
:只有当 key 不存在时才设置成功PX 30000
:锁 30 秒后自动过期(防止宕机后死锁)
用 Java 来实现就是:
String lockKey = "lock:order:123";
String uniqueId = UUID.randomUUID().toString();
boolean isLocked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, uniqueId, 10, TimeUnit.SECONDS);
if (isLocked) {
try {
// 执行业务逻辑
} finally {
// 释放锁
}
}
✅ 解锁
释放锁时,需要判断当前锁是不是自己的,防止误删别人的锁:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
🚨 不能直接 DEL
,要验证 value
是否匹配!
三、Redis 分布式锁的完整流程(Mermaid 图示)
四、常见问题与高级方案
❌ 问题 1:服务执行时间超过过期时间,锁被误释放
使用 setnx 创建分布式锁时,虽然设置过期时间可以避免死锁问题,但可能存在这样的问题:线程 A 获取锁后开始任务,如果任务执行时间超过锁的过期时间,锁会提前释放,导致线程 B 也获取了锁并开始执行任务。这会破坏锁的独占性,导致并发访问资源,进而造成数据不一致。
比如你设置了 30s 锁,但方法执行了 35s,Redis 自动释放锁后被别人抢了锁,你还在操作资源。
✅ 解决方案:锁续期机制(如 Redisson)
可以引入锁的自动续约机制,在任务执行过程中定期续期,确保锁在任务完成之前不会过期。
使用定时任务 / 看门狗机制,在锁持有期间自动延长过期时间,直到任务结束。
比如说 Redisson 的 RedissonLock 就支持自动续期,通过看门狗机制定期续期锁的有效期。
❌ 问题 2:释放别人的锁
多个线程用同一个锁 key,如果不验证 value
就删,容易释放掉其他线程加的锁。
✅ 解决方案:使用 UUID 标识锁主,删除前验证 value
,使用 Lua 脚本保证原子性。
❌ 问题 3:Redis 单点故障
如果 Redis 宕机了,锁信息全丢,系统就不安全了。
✅ 解决方案:使用 RedLock(Redis 官方提出的分布式锁算法)
五、RedLock:分布式锁的更高级实现
🔁 原理:
Redlock 是 Redis 作者提出的一种分布式锁实现方案,用于确保在分布式环境下安全可靠地获取锁。它的目标是在分布式系统中提供一种高可用、高容错的锁机制,确保在同一时刻,只有一个客户端能够成功获得锁,从而实现对共享资源的互斥访问。
Redisson 中的 RedLock 是基于 RedissonMultiLock(联锁)实现的。
使用多个 Redis 实例(最好在不同物理机):
- 尝试在 N 个 Redis 实例中至少 超过半数节点加锁成功(如 3 个成功 2 个);
- 所有操作都带过期时间,如果指定了锁的持有时间(leaseTime),在成功获取锁后,Redlock 会为锁进行续期,以防止锁在操作完成之前意外失效。
- 加锁和释放锁都要使用唯一标识 + 原子性校验。
缺点:实现复杂、网络抖动会影响判断,容易导致锁的获取失败,Redis 官方也不推荐生产直接使用 RedLock,推荐使用 Redisson 等封装库。
六、Redisson:企业级分布式锁推荐方式 ✅
Redisson
是一个成熟的 Redis Java 客户端,封装了分布式锁实现:
RLock lock = redissonClient.getLock("lockKey");
try {
// 默认锁30秒,自动续期机制(看门狗)
lock.lock();
// 执行业务逻辑
} finally {
lock.unlock();
}
实现源码在 RedissonLock 类中,通过 Lua 脚本封装 Redis 命令来实现,比如说 tryLockInnerAsync 源码:
其中 hincrby 命令用于对哈希表中的字段值执行自增操作,pexpire 命令用于设置键的过期时间。
✅ 特点:
- 自动续期,看门狗机制防止过期
- 支持可重入锁、公平锁、读写锁等丰富功能
- 支持 RedLock 多节点锁方案
七、总结对比
实现方式 | 原理 | 优点 | 缺点 |
---|---|---|---|
SETNX + EXPIRE | 基础加锁 | 简单易实现 | 需要手动续期、防误删 |
Lua 脚本解锁 | 原子性校验 | 安全释放 | 需要脚本支持 |
RedLock | 多节点投票机制 | 容错高 | 实现复杂,不推荐自研 |
Redisson | 封装式实现 | 简单、安全、功能全 | 引入第三方依赖 |
八、面试答题技巧(建议结构)
1. Redis 使用 SETNX + PX 实现锁
2. 需要用 Lua 脚本验证后删除,防止误删
3. 存在执行时间过长、锁提前过期问题
4. 可用 Redisson 解决自动续期和安全释放问题
5. 高可用方案可以用 RedLock(了解即可)