一句话总结
Redis分布式锁推荐SET key value NX EX原子操作实现,释放时用Lua脚本校验value。Redisson看门狗自动续期防止锁提前释放。可重入锁用Hash结构记录持有次数。
初级理解
基础实现:
# 错误做法:SETNX + EXPIRE 分两步,非原子操作
SETNX lock_key 1
EXPIRE lock_key 30
# 正确做法:一条命令原子加锁
SET lock_key 1 NX EX 30
释放锁:
# Lua脚本校验value后再删除,防止误删他人锁
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
一句话总结:SET NX EX原子加锁 + Lua脚本校验释放。
中级深入
Redisson看门狗机制:
• 自动续期:调用lock()不指定过期时间时,默认30秒
• 定时续期:每隔10秒(30/3)检查锁是否仍被持有,是则重置为30秒
• 作用:防止业务未完成时锁过期释放
可重入锁:
Redisson使用Hash结构:key -> {threadId: count}
同一线程重入时count+1,释放时count-1,为0时才真正删除
注意:Redisson看门狗只在未指定过期时间时生效。指定了过期时间,看门狗不会续期。
高级拓展
红锁(RedLock):
# 针对Redis主从切换导致锁丢失的问题
1. 向N个独立Redis节点同时加锁
2. 超过半数成功才算加锁成功
3. 锁的有效时间 = 过期时间 - 获取锁的时间
# 争议:增加了复杂性,一般场景慎用
Redisson vs Jedis:
Redisson分布式锁示例:
RLock lock = redissonClient.getLock("myLock");
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
// 执行业务逻辑
}
lock.unlock();
面试加分项:能说出Redisson看门狗原理、可重入锁实现、红锁争议,说明你对Redis分布式锁有深入理解。
实战场景
场景:Redisson分布式锁实战
// 订单超时未支付自动取消
@Scheduled(fixedRate = 60000)
public void cancelExpiredOrders() {
List orders = orderMapper.selectExpiredOrders(LocalDateTime.now().minusMinutes(30));
for (Order order : orders) {
RLock lock = redissonClient.getLock("order:cancel:" + order.getId());
try {
if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
Order current = orderMapper.selectById(order.getId());
if ("PENDING".equals(current.getStatus())) {
orderMapper.updateStatus(order.getId(), "PENDING", "CANCELLED");
stockService.releaseStock(order.getProductId(), order.getQuantity());
}
}
} finally {
if (lock.isHeldByCurrentThread()) lock.unlock();
}
}
}
面试模拟
Q:Redisson看门狗是如何实现自动续期的?
A:1. 调用lock()不指定过期时间时,默认30秒;2. 后台定时任务每隔10秒检查锁是否仍被持有;3. 如果仍被持有,重置过期时间为30秒;4. 线程释放锁或宕机后,定时任务停止,锁30秒后自动释放。
Q:Redis分布式锁为什么要用Lua脚本释放?
A:因为GET和DEL是两个操作,如果分开执行,在GET和DEL之间锁可能已过期,其他线程已获取锁,此时DEL会误删其他线程的锁。Lua脚本可以保证GET+DEL的原子性,先校验value再删除。