一、基于Redis分布式锁(项目最常用,Redisson)
- 原生SET命令(原子加锁)
// key:锁标识 value:唯一标识(UUID) NX不存在才设置 EX过期时间
SET lock_key uuid NX EX 30
• NX:互斥;EX:自动过期防死锁
• 解锁:必须校验value再DEL(避免删别人的锁),用Lua脚本原子解锁
if redis.call('get',KEYS1)==ARGV1 then return redis.call('del',KEYS1) else return 0 end
原生痛点:无锁续期、主从切换丢锁。
-
Redisson(生产首选,封装好)
RLock lock = redissonClient.getLock("stock_lock");
// 尝试加锁,等待10s,持有30s自动过期
boolean acquire = lock.tryLock(10,30,TimeUnit.SECONDS);
if(acquire){
try{
//业务
}finally {
if(lock.isHeldByCurrentThread()) lock.unlock();
}
}
核心特性
-
看门狗自动续期:业务没执行完,定时续过期时间,防止业务没做完锁过期
-
可重入锁:同一个线程多次加锁不会死锁
-
支持公平锁、读写锁
-
RedLock解决Redis集群主从宕机丢锁问题(多节点加锁过半才算成功)
二、Zookeeper临时节点锁
-
抢锁:创建/lock临时有序节点
-
最小序号节点获得锁;其他节点监听前序节点
-
释放:断开连接临时节点自动删除,天然防死锁
• 优点:可靠性高、可重入、自动释放
• 缺点:性能差,zk频繁创建节点,高并发不用
三、数据库实现分布式锁
- 悲观锁:select ... for update
事务内锁定行,其他事务阻塞,依赖数据库行锁;长事务阻塞严重。
- 乐观锁:version版本号
update stock set num=num-1,version=version+1 where id=1 and version=oldVersion;
更新行数=0代表抢占锁失败,重试;适合高并发短事务。
四、选型对比
-
高并发、高性能 → Redisson(Redis)
-
可靠性优先、并发不高 → Zookeeper
-
无中间件,简单项目 → 数据库乐观锁
五、高频面试坑
-
死锁:加锁必须设置过期时间
-
锁失效误删:存唯一value,只有加锁线程能删锁
-
主从失效丢锁:Redis主没同步到从就宕机,RedLock或ZK规避
-
可重入:原生Redis不支持,Redisson内部计数实现可重入