📖 开场:上厕所的各种意外
想象你去公共厕所 🚽:
场景1:忘记锁门
你进去 → 忘记反锁 🚪
别人推门进来 → 尴尬!😱
→ 对应问题:没有加锁
场景2:门锁坏了
你进去反锁 → 门锁故障 🔒
出不去了!💀
→ 对应问题:死锁
场景3:别人把你的锁打开了
你进去反锁 → 上厕所中...
清洁工误以为没人 → 用万能钥匙打开 🔑
→ 对应问题:误删别人的锁
场景4:时间太长被强制开门
你进去反锁 → 上厕所中...(时间太长)
10分钟后 → 门锁自动打开(防止有人晕倒)
别人进来 → 尴尬!😱
→ 对应问题:锁过期
这就是分布式锁的细节问题!
🎯 Redis分布式锁的8大问题
问题1:原子性问题 ⚛️
错误实现
/**
* ❌ 错误:SETNX和EXPIRE不是原子操作
*/
public boolean tryLock(String lockKey, String holder) {
// 步骤1:SETNX设置锁
Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, holder);
if (Boolean.TRUE.equals(success)) {
// 步骤2:设置过期时间
redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
return true;
}
return false;
}
问题:
步骤1:SETNX成功 ✅
↓
【此时服务器宕机】💀
↓
步骤2:EXPIRE没有执行 ❌
↓
锁永远不会过期 → 死锁!💀
正确实现
/**
* ✅ 正确:SETNX和EXPIRE原子操作
*/
public boolean tryLock(String lockKey, String holder, int expireSeconds) {
// ⭐ Redis 2.6.12+支持:SET key value EX seconds NX
// 一条命令完成SETNX + EXPIRE,保证原子性
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, holder, expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
原理:
SET lock_key uuid EX 10 NX
参数说明:
- NX:Not eXists,key不存在时才设置(SETNX)
- EX 10:10秒后过期(EXPIRE)
- 一条命令,原子操作 ✅
问题2:误删别人的锁 🔐
错误实现
/**
* ❌ 错误:直接删除锁,可能删除别人的锁
*/
public void unlock(String lockKey) {
redisTemplate.delete(lockKey); // ❌ 直接删除
}
问题场景:
时间线:
10:00:00 线程A获取锁(10秒过期)
10:00:05 线程A处理业务(慢)
10:00:10 线程A的锁过期,自动删除
10:00:11 线程B获取锁 ✅
10:00:12 线程A处理完成,调用unlock()
→ 删除了线程B的锁!❌
结果:
- 线程B以为自己持有锁,但锁已被删除
- 线程C也能获取锁
- 线程B和C同时执行业务 → 并发问题!😱
正确实现(Lua脚本)
/**
* ✅ 正确:检查是否是自己的锁,再删除
*/
public void unlock(String lockKey, String holder) {
// ⭐ Lua脚本:原子操作
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
// 执行脚本
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
holder
);
}
原理:
Lua脚本在Redis中是原子执行的
步骤:
1. GET lock_key → 获取当前holder
2. 比较是否等于传入的holder
3. 相等才执行DEL lock_key
4. 整个过程原子执行 ✅
为什么不用下面的方式?
/**
* ❌ 错误:GET和DEL分两步,不是原子操作
*/
public void unlock(String lockKey, String holder) {
String currentHolder = redisTemplate.opsForValue().get(lockKey);
if (holder.equals(currentHolder)) {
// ⚠️ 问题:这里可能锁已经过期,被别人获取了
redisTemplate.delete(lockKey); // 可能删除的是别人的锁
}
}
问题:
时间线:
10:00:00 线程A的锁(10秒过期)
10:00:10 GET lock_key → 得到线程A的holder ✅
10:00:10.001 锁过期,自动删除
10:00:10.002 线程B获取锁 ✅
10:00:10.003 线程A执行DEL lock_key
→ 删除了线程B的锁!❌
结论:GET和DEL之间有时间差,不是原子操作 ❌
问题3:锁过期问题 ⏰
场景
获取锁(10秒过期)
↓
业务处理中...(5秒)
↓
业务处理中...(5秒)
↓
【锁过期,自动删除】❌
↓
其他线程获取锁 ✅
↓
原线程继续处理...
↓
两个线程同时执行业务 → 并发问题!😱
解决方案1:设置更长的过期时间 ❌
// ❌ 不推荐:设置很长的过期时间(如1小时)
redisTemplate.opsForValue()
.setIfAbsent(lockKey, holder, 3600, TimeUnit.SECONDS);
问题:
- 业务异常时,锁要等1小时才释放 → 系统停摆!
- 治标不治本
解决方案2:Redisson的Watch Dog ✅
@Service
@Slf4j
public class OrderService {
@Autowired
private RedissonClient redissonClient;
public void processOrder(Long orderId) {
RLock lock = redissonClient.getLock("order:lock:" + orderId);
try {
// ⭐ 获取锁(不指定过期时间,启动Watch Dog)
lock.lock();
log.info("获取锁成功,开始处理订单");
// ⭐ 业务处理(可能需要很长时间)
doSomethingTakesLongTime();
log.info("订单处理完成");
} finally {
// ⭐ 释放锁(停止Watch Dog)
lock.unlock();
}
}
private void doSomethingTakesLongTime() {
// 耗时操作...
}
}
Watch Dog原理:
获取锁(默认30秒过期)
↓
启动Watch Dog后台线程 🐕
↓
每10秒(30秒的1/3)检查一次
↓
如果锁还在持有 → 重置过期时间为30秒 🔄
↓
业务处理完成 → 释放锁 → 停止Watch Dog ✅
Watch Dog源码分析:
// Redisson源码(简化版)
public class RedissonLock {
private void scheduleExpirationRenewal(long threadId) {
// 创建定时任务
Timeout task = commandExecutor.getConnectionManager()
.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) {
// ⭐ 续期逻辑
renewExpiration();
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
// ↑
// 每10秒执行一次(30秒的1/3)
}
private void renewExpiration() {
// ⭐ 重置过期时间为30秒
commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE,
"if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " +
" redis.call('pexpire', KEYS[1], ARGV[1]); " +
" return 1; " +
"else " +
" return 0; " +
"end",
Collections.singletonList(getName()),
internalLockLeaseTime, getLockName(threadId));
}
}
解决方案3:手动续期(不推荐)❌
public void processOrder(Long orderId) {
String lockKey = "order:lock:" + orderId;
String holder = UUID.randomUUID().toString();
// 启动续期线程
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(() -> {
// 每5秒续期一次
redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
}, 5, 5, TimeUnit.SECONDS);
try {
// 业务处理
doSomething();
} finally {
// 停止续期
future.cancel(true);
scheduler.shutdown();
// 释放锁
unlock(lockKey, holder);
}
}
问题:
- 代码复杂
- 需要管理线程池
- 不如Redisson的Watch Dog优雅
问题4:可重入性问题 🔄
什么是可重入锁?
同一个线程可以多次获取同一个锁
场景:
methodA() {
获取锁A
↓
调用methodB()
↓
methodB() {
再次获取锁A ← ⭐ 可重入
↓
业务逻辑
↓
释放锁A
}
↓
释放锁A
}
简单实现(不可重入)❌
/**
* ❌ 不支持可重入
*/
public boolean tryLock(String lockKey, String holder) {
return redisTemplate.opsForValue()
.setIfAbsent(lockKey, holder, 10, TimeUnit.SECONDS);
}
public void methodA() {
tryLock("lock", "thread-1"); // 成功 ✅
methodB(); // 调用methodB
unlock("lock", "thread-1");
}
public void methodB() {
tryLock("lock", "thread-1"); // 失败!❌(锁已存在)
// 业务逻辑无法执行
unlock("lock", "thread-1");
}
Redisson实现(可重入)✅
@Service
public class OrderService {
@Autowired
private RedissonClient redissonClient;
public void methodA(Long orderId) {
RLock lock = redissonClient.getLock("order:lock:" + orderId);
try {
lock.lock(); // ⭐ 第一次获取锁
log.info("methodA获取锁");
methodB(orderId); // 调用methodB
} finally {
lock.unlock();
}
}
public void methodB(Long orderId) {
RLock lock = redissonClient.getLock("order:lock:" + orderId);
try {
lock.lock(); // ⭐ 再次获取锁(可重入)✅
log.info("methodB获取锁");
// 业务逻辑
} finally {
lock.unlock();
}
}
}
Redisson可重入锁原理:
Redis数据结构:
lock_key = {
"thread-1": 1, // 计数器
"expire": 30000
}
获取锁:
1. 如果key不存在,创建并设置计数器为1
2. 如果key存在且holder是自己,计数器+1(可重入)
3. 如果key存在且holder不是自己,获取失败
释放锁:
1. 计数器-1
2. 如果计数器==0,删除key
3. 如果计数器>0,保留key
Lua脚本实现:
-- 获取锁
if redis.call('exists', KEYS[1]) == 0 then
-- 锁不存在,创建
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return 1;
elseif redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
-- 锁存在且是自己持有,计数器+1
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return 1;
else
-- 锁被别人持有
return 0;
end
-- 释放锁
if redis.call('hexists', KEYS[1], ARGV[2]) == 0 then
-- 锁不是自己的
return nil;
end
-- 计数器-1
local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1);
if counter > 0 then
-- 还有重入,只重置过期时间
redis.call('pexpire', KEYS[1], ARGV[1]);
return 0;
else
-- 计数器为0,删除锁
redis.call('del', KEYS[1]);
return 1;
end
问题5:Redis主从切换问题 🔄
场景
Redis主从架构:
Master(主)──→ Slave(从)
(异步复制)
问题:
10:00:00 Client A 获取锁(写入Master)✅
10:00:01 【Master宕机】💀(锁还没同步到Slave)
10:00:02 【Slave升级为Master】
10:00:03 Client B 获取锁(新Master上没有锁)✅
结果:Client A 和 Client B 同时持有锁!❌
解决方案:Redlock算法 🔴
原理:在多个独立的Redis实例上获取锁
5个独立的Redis实例(不是主从)
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│Redis│ │Redis│ │Redis│ │Redis│ │Redis│
│ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘
获取锁的步骤:
1. 获取当前时间t1
2. 依次在5个Redis上获取锁(超时时间5ms)
3. 获取当前时间t2
4. 判断是否成功:
- 在大多数实例(3个)上获取到锁 ✅
- 获取锁的耗时(t2-t1) < 锁的有效时间
5. 如果成功,锁的有效时间 = 原有效时间 - 获取锁的耗时
6. 如果失败,释放所有已获取的锁
Redisson实现:
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// ⭐ 配置5个独立的Redis实例
config.useReplicatedServers()
.addNodeAddress(
"redis://127.0.0.1:6379",
"redis://127.0.0.1:6380",
"redis://127.0.0.1:6381",
"redis://127.0.0.1:6382",
"redis://127.0.0.1:6383"
);
return Redisson.create(config);
}
}
@Service
public class OrderService {
@Autowired
private RedissonClient redissonClient;
public void processOrder(Long orderId) {
// ⭐ 获取5个独立的锁
RLock lock1 = redissonClient.getLock("lock:" + orderId);
RLock lock2 = redissonClient.getLock("lock:" + orderId);
RLock lock3 = redissonClient.getLock("lock:" + orderId);
RLock lock4 = redissonClient.getLock("lock:" + orderId);
RLock lock5 = redissonClient.getLock("lock:" + orderId);
// ⭐ RedissonRedLock(Redlock算法)
RLock redLock = redissonClient.getRedLock(lock1, lock2, lock3, lock4, lock5);
try {
redLock.lock();
// 业务逻辑
} finally {
redLock.unlock();
}
}
}
优缺点:
- ✅ 高可用(不受单个Redis故障影响)
- ✅ 不会丢锁(大多数实例都有锁)
- ❌ 成本高(需要5个独立的Redis实例)
- ❌ 性能较差(需要在多个实例上操作)
问题6:锁粒度问题 🎯
粒度过大(性能差)❌
/**
* ❌ 错误:所有订单共用一个锁
*/
public void processOrder(Long orderId) {
RLock lock = redissonClient.getLock("order:lock"); // 全局锁
try {
lock.lock();
// 处理订单
} finally {
lock.unlock();
}
}
结果:
- 订单1处理中...(其他订单都要等待)
- 并发性极差 ❌
粒度合适(推荐)✅
/**
* ✅ 正确:每个订单一个锁
*/
public void processOrder(Long orderId) {
RLock lock = redissonClient.getLock("order:lock:" + orderId); // ⭐ 按订单ID加锁
try {
lock.lock();
// 处理订单
} finally {
lock.unlock();
}
}
结果:
- 订单1处理中...(其他订单不受影响)✅
- 并发性好 ✅
粒度过小(内存浪费)⚠️
/**
* ⚠️ 注意:锁太多,Redis内存消耗大
*/
public void processOrderItem(Long orderId, Long itemId) {
// 每个商品明细一个锁
RLock lock = redissonClient.getLock("order:lock:" + orderId + ":item:" + itemId);
try {
lock.lock();
// 处理商品明细
} finally {
lock.unlock();
}
}
结果:
- 锁太多,Redis内存消耗大
- 一般按订单ID加锁就够了
问题7:锁等待问题 ⏰
自旋等待(忙等待)❌
/**
* ❌ 不推荐:自旋等待,浪费CPU
*/
public boolean tryLockWithSpin(String lockKey, String holder, int retryTimes) {
for (int i = 0; i < retryTimes; i++) {
if (tryLock(lockKey, holder)) {
return true;
}
// ❌ 立即重试,CPU空转
}
return false;
}
间隔重试(推荐)✅
/**
* ✅ 推荐:间隔重试,避免CPU空转
*/
public boolean tryLockWithRetry(String lockKey, String holder,
int retryTimes, long retryInterval) {
for (int i = 0; i < retryTimes; i++) {
if (tryLock(lockKey, holder)) {
return true;
}
// ⭐ 重试前等待
if (i < retryTimes - 1) {
try {
Thread.sleep(retryInterval); // 休眠一段时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
return false;
}
Redisson的tryLock(推荐)✅
/**
* ✅ 推荐:Redisson的tryLock
*/
public void processOrder(Long orderId) {
RLock lock = redissonClient.getLock("order:lock:" + orderId);
try {
// ⭐ tryLock(等待时间, 过期时间, 时间单位)
// 等待时间内会自动重试,使用Redis的发布订阅机制,不浪费CPU
boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!acquired) {
log.warn("获取锁失败");
return;
}
// 业务逻辑
} catch (InterruptedException e) {
log.error("获取锁被中断", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
Redisson的优化:
1. 使用Redis的发布订阅(Pub/Sub)
2. 锁释放时,发布消息通知等待的客户端
3. 等待的客户端收到通知后,立即尝试获取锁
4. 避免了轮询,节省CPU ✅
问题8:锁超时问题 ⏱️
场景
设置锁的过期时间:10秒
实际业务处理时间:15秒
问题:
0秒:获取锁 ✅
5秒:业务处理中...
10秒:锁过期,自动释放 ❌
11秒:其他线程获取锁 ✅
15秒:原线程处理完成,尝试释放锁
→ 释放的是别人的锁(如果没有holder校验)❌
解决方案总结
| 方案 | 优点 | 缺点 | 推荐 |
|---|---|---|---|
| 设置很长的过期时间 | 简单 | 异常时锁长时间不释放 | ❌ |
| Redisson Watch Dog | 自动续期,优雅 | 需要使用Redisson | ✅ |
| 手动续期 | 灵活 | 代码复杂,容易出错 | ❌ |
| 评估业务时间 | 简单 | 难以准确评估 | ⚠️ |
🎯 Redis分布式锁的完整实现
自己实现(不推荐)
@Component
@Slf4j
public class RedisDistributedLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 获取锁
*/
public boolean tryLock(String lockKey, String holder, int expireSeconds) {
// ⭐ SETNX + 过期时间(原子操作)
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, holder, expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
/**
* 释放锁
*/
public void unlock(String lockKey, String holder) {
// ⭐ Lua脚本:检查holder后删除(原子操作)
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
holder
);
}
}
问题:
- 不支持可重入 ❌
- 不支持自动续期 ❌
- 不支持锁等待优化 ❌
使用Redisson(强烈推荐)⭐⭐⭐
@Service
@Slf4j
public class OrderService {
@Autowired
private RedissonClient redissonClient;
/**
* 扣减库存(完整示例)
*/
public boolean deductStock(Long productId, int quantity) {
// ⭐ 获取锁对象
RLock lock = redissonClient.getLock("stock:lock:" + productId);
try {
// ⭐ 尝试获取锁
// tryLock(等待时间, 过期时间, 时间单位)
boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!acquired) {
log.warn("获取锁失败,库存扣减失败");
return false;
}
log.info("获取锁成功,开始扣减库存");
// ⭐ 业务逻辑
int stock = getStock(productId);
if (stock < quantity) {
log.warn("库存不足: stock={}, need={}", stock, quantity);
return false;
}
setStock(productId, stock - quantity);
log.info("库存扣减成功: productId={}, quantity={}, remaining={}",
productId, quantity, stock - quantity);
return true;
} catch (InterruptedException e) {
log.error("获取锁被中断", e);
return false;
} finally {
// ⭐ 释放锁(检查是否是当前线程持有的锁)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("释放锁成功");
}
}
}
private int getStock(Long productId) {
// 从Redis或数据库读取库存
String stock = redisTemplate.opsForValue().get("stock:" + productId);
return stock != null ? Integer.parseInt(stock) : 0;
}
private void setStock(Long productId, int stock) {
redisTemplate.opsForValue().set("stock:" + productId, String.valueOf(stock));
}
}
Redisson的优势:
- ✅ 支持可重入锁
- ✅ 支持Watch Dog自动续期
- ✅ 支持公平锁、读写锁、信号量等多种锁
- ✅ 使用Pub/Sub优化锁等待
- ✅ 代码简洁,易用
🎓 面试题速答
Q1: Redis分布式锁有哪些问题?
A: 8大问题:
- 原子性问题:SETNX和EXPIRE分两步 → 用SET NX EX一条命令
- 误删别人的锁:直接DELETE → 用Lua脚本校验holder
- 锁过期问题:业务处理时间长 → Redisson Watch Dog自动续期
- 可重入性问题:不支持重入 → Redisson可重入锁
- 主从切换问题:主从异步复制 → Redlock算法
- 锁粒度问题:粒度太大或太小 → 按业务对象ID加锁
- 锁等待问题:自旋浪费CPU → Redisson Pub/Sub优化
- 锁超时问题:过期时间难评估 → Watch Dog自动续期
Q2: 如何防止误删别人的锁?
A: Lua脚本校验holder:
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
原理:
- GET lock_key,获取当前holder
- 比较是否等于传入的holder
- 相等才执行DEL
- Lua脚本原子执行 ✅
Q3: Redisson的Watch Dog是什么?
A: Watch Dog = 自动续期机制 🐕
// 获取锁时不指定过期时间
lock.lock(); // 默认30秒,启动Watch Dog
// Watch Dog每10秒检查一次
// 如果锁还在持有,重置过期时间为30秒
作用:防止业务处理时间过长,锁自动过期
原理:后台线程定时检查,自动续期
Q4: Redis主从切换会丢锁吗?
A: 会!
场景:
Client A获取锁(写入Master)✅
↓
Master宕机(锁还没同步到Slave)💀
↓
Slave升级为Master(没有锁记录)
↓
Client B获取锁(成功)✅
↓
Client A和B同时持有锁 ❌
解决方案:Redlock算法
- 在多个独立的Redis实例上获取锁
- 大多数实例获取成功才算成功
- 不受单个Redis故障影响
Q5: 为什么要用Redisson而不是自己实现?
A: Redisson的优势:
- 可重入锁:支持同一线程多次获取锁
- Watch Dog:自动续期,防止锁过期
- Pub/Sub优化:不浪费CPU轮询
- 多种锁类型:公平锁、读写锁、信号量等
- 久经考验:生产环境广泛使用,稳定可靠
自己实现的问题:
- 不支持可重入 ❌
- 不支持自动续期 ❌
- 容易出现细节问题 ❌
结论:推荐使用Redisson ⭐⭐⭐
Q6: 锁的过期时间如何设置?
A: 三种方案:
-
评估业务时间 + 安全系数:
业务处理时间:2秒 安全系数:3倍 过期时间:6秒缺点:难以准确评估
-
使用Redisson(不指定过期时间):
lock.lock(); // 启动Watch Dog,自动续期优点:不需要评估业务时间 ✅
-
使用tryLock指定等待时间和过期时间:
lock.tryLock(3, 10, TimeUnit.SECONDS); // ↑ ↑ // 等待3秒 过期10秒优点:灵活控制
推荐:使用Redisson的lock()或tryLock()
🎬 总结
Redis分布式锁的8大问题和解决方案
┌────────────────────────────────────────────┐
│ 1. 原子性问题 │
│ SET NX EX(一条命令)✅ │
└────────────────────────────────────────────┘
┌────────────────────────────────────────────┐
│ 2. 误删别人的锁 │
│ Lua脚本校验holder ✅ │
└────────────────────────────────────────────┘
┌────────────────────────────────────────────┐
│ 3. 锁过期问题 │
│ Redisson Watch Dog自动续期 ✅ │
└────────────────────────────────────────────┘
┌────────────────────────────────────────────┐
│ 4. 可重入性问题 │
│ Redisson可重入锁 ✅ │
└────────────────────────────────────────────┘
┌────────────────────────────────────────────┐
│ 5. 主从切换问题 │
│ Redlock算法 ✅ │
└────────────────────────────────────────────┘
使用Redisson,解决大部分问题!⭐
🎉 恭喜你!
你已经完全掌握了Redis分布式锁的细节和问题!🎊
核心要点:
- 原子性:SET NX EX一条命令
- 防误删:Lua脚本校验holder
- 防过期:Watch Dog自动续期
- 可重入:Redisson内置支持
- 高可用:Redlock算法
下次面试,这样回答:
"Redis分布式锁主要有8大问题,其中最关键的是原子性、误删别人的锁和锁过期问题。
原子性问题通过SET NX EX一条命令解决,保证SETNX和设置过期时间是原子操作。
误删问题通过Lua脚本解决,释放锁时先检查holder是否匹配,只删除自己的锁。
锁过期问题通过Redisson的Watch Dog解决,后台线程每10秒检查一次,如果锁还在持有就自动续期到30秒。
我们项目使用Redisson实现分布式锁,它内置了可重入锁、Watch Dog自动续期、Pub/Sub优化等特性,代码简洁且久经考验。对于金融等高可用场景,我们会使用Redlock算法,在多个独立的Redis实例上获取锁。"
面试官:👍 "很好!你对Redis分布式锁的细节理解很透彻!"
本文完 🎬
上一篇: 195-分布式锁的实现方式.md
下一篇: 197-Zookeeper实现分布式锁的原理.md
作者注:写完这篇,我对Redis分布式锁的理解又深了一层!🔒
如果这篇文章对你有帮助,请给我一个Star⭐!