一、分布式锁定义分布式锁是一种用于分布式系统中的同步机制用于在多个进程/节点之间协调对共享资源的访问确保在同一时刻只有一个节点能够执行特定的临界区代码或操纵共享资源。二、什么时候用场景说明防止重复处理定时任务在多台机器上部署避免重复执行保证幂等性接口被重复调用时确保只处理一次库存扣减秒杀、抢购场景下防止超卖分布式事务保证跨服务的操作原子性缓存重建防止缓存击穿时多个线程同时重建缓存三、作用互斥性 - 同一时刻只有一个客户端能持有锁防死锁 - 锁有超时机制避免永久阻塞高可用 - 锁服务本身需要高可用可重入 - 同一线程可重复获取锁可选四、常见实现方式4.1.基于Redis最常用//Redisson实现 RLock lock redissonClient.getLock(lock:order:oid); try{ boolean locked lock.tryLock(3,30,TimeUnit.SECONDS); if(locked){ //执行业务逻辑 } } finally{ lock.unlock(); }原理利用SET key value NX EX seconds 原子命令4.2.基于Zookeeper//Curator实现 InterProcessMutex lock new InterProcessMutex(curatorFramewoek,/locks/order); try{ if(lock.acquire(3,TimeUnit.SECONDS)){ //执行业务逻辑 } } finally { lock.release(); }原理利用临时顺序节点和Watcher机制4.3.基于数据库--利用唯一索引或乐观锁 INSERT INTO distribted_lock(lock_name,expire_time) VALUES(order_123,NOW()INTERVAL 30 SECOND);4.4.对比特性RedisZookeeper数据库性能高中低可靠性中主从切换可能丢锁高CP中实现复杂度简单较复杂简单适用场景大多数场景强一致性要求简单场景4.5.关键注意事项锁续期 - 业务执行时间超过锁超时时间的时候需要自动续期可重入性 - 同一线程多次获取锁应该成功锁的粒度和命名 - 越小越好避免竞争释放锁 - 必须确保锁被释放使用try-finally五、锁不可重入5.1.什么是可重入锁可重入锁Reentrant Lock是指同一线程可以多次获取同一把锁而不会导致死锁。5.2.不可重入的问题示例//假设当前锁已被线程A持有 public void methodA(){ lock.lock();//线程A第一次获取锁 - 成功 methodB();//调用另一个也需要锁的方法 } public void methodB(){ lock.lock();//线程A第二次获取锁 - 如果是不可重入锁这里会阻塞 //do something lock.unlock(); }5.3.不可重入锁的实现特点public boolean tryLock(String lockKey){ //只判断 key 是否存在 boolean exists redis.exists(lockKey); if(exists){ return false;//不管是不是自己持有的都返回失败 } redis.set(lockKey, threadId); return true; }5.4.可重入锁的实现方式//可重入锁的实现思路 public class ReentrantDistributedLock{ //使用ThreadLocal记录当前线程的重入次数 private ThreadLocalMapString, Integer holdCount new ThreadLocal(); public boolean tryLock(String lockKey){ String currentThreadId Thread.currentThread().getId(); //1.先检查是否是自己持有的锁 String lockValue redis.get(lockKey); if(lockValue !null lockValue.equals(currentThreadId)){ //是自己持有的重入次数1 incrementHoldCount(lockKey); return true; } //2.尝试获取锁 boolean locked redis.set(lockKey,currentThreadId,NX,EX,30); if(locked){ setHoldCount(lockKey,1); } return locked; } public void unlock(String lockKey){ //重入次数-1 int count decrementHoldCount(lockKey); //只有重入次数减到0 才真正释放锁 if(count0){ redis.del(lockKey); removeHoldCount(lockKey); } } }可重入锁的数据结构Redis中存储lockKey: order:123:lock - value: thread-123lockKey: order:123:lock:count - value: 2 重入次数六、看门狗续期6.1.问题背景业务执行时间不确定比如操作1查询数据库 - 2秒操作2调用外部接口 - 10秒网络抖动可能更久操作3更新数据库 - 2秒操作4发送消息 - 3秒总计可能超过15秒风险业务还没执行完锁已经过期被自动释放其他线程可以获取锁导致并发问题。6.2.看门狗机制原理时间线0s - 加锁(30s)5s - 续期(30s)10s - 续期(30s)15s - 续期(30s)20s - 续期(30s)25s - 续期(30s)30s - 解锁public class WatchDogDistributedLock{ //定时任务线程池 private ScheduledExecutorService scheduler Executors.newScheduledThreadPool(10); //记录每个锁的续期任务 private ConcurrentHashMapString, ScheduledFuture? watchDogTasks new ConcurrentHashMap(); public boolean lock(String lockKey,long leaseTime){ String threadId Thread.currentThread().getId(); //1.尝试获取锁 boolean locked redis.set(lockKey,threadId,NX,EX,leaseTime); if(locked){ //2.启动看门狗在锁过期前续期 startWatchDog(lockKey, leaseTime); } return locked; } private void startWatchDog(String lockKey, long leaseTime){ //每隔 leaseTime/3 检查一次提前续期 long checkInterval leaseTime / 3; ScheduledFuture? future scheduler.scheduleAtFixedRate(() - { //检查锁是否还是自己持有 String currentValue redis.get(lockKey); if(Thread.currentThread().getId().equals(currentValue)){ //续期重新设置过期时间 redis.expire(lockKey, leaseTime); //看门狗续期成功lockKey } else { //锁已被其他线程获取停止看门狗 stopWatchDog(lockKey); } }, checkInterval, checkInterval, TimeUnit.SECONDS); watchDogTasks.put(lockKey, future); } }6.3.Redisson的看门狗实现推荐使用Redisson它内置了完整的看门狗机制//Redisson配置 Config config new Config(); config.useSingleServer().setAddress(redis://127.0.0.1:6379); RedissonClient redisson Redisson.create(config); //使用RLock RLock lock redisson.getLock(order:123:lock); try{ //不指定时间启动看门狗默认30s过期每10s续期 lock.lock(); //或者指定时间不启动看门狗到时间自动释放 //lock.lock(10,TimeUnit.SECONDS); //执行业务逻辑 } finally { lock.unlock(); }6.4.对比总结特性一般实现带看门狗的实现锁过期固定时间可能提前释放业务执行期间自动续期适用场景业务执行时间确定且短业务执行时间不确定复杂度简单较复杂需要定时任务可靠性低高如果业务复杂或执行时间不确定建议使用Redisson替代或者增加看门狗续期机制设置合理的超时时间宁可设置长一点也不要太短