当前位置: 首页 > news >正文

Redis分布式锁从入门到精通:从SETNX到Redisson看门狗机制

Redis分布式锁从入门到精通:从SETNX到Redisson看门狗机制

    • 引言
    • 1. 分布式锁的核心要求
    • 2. 基于Redis的简易分布式锁实现
      • 2.1 第一阶段:SETNX + EXPIRE(有问题!)
      • 2.2 第二阶段:SET原子操作(正确基础版)
      • 2.3 第三阶段:安全解锁(Lua脚本)
      • 2.4 完整基础代码示例(Java版)
      • 2.5 基础版流程图
    • 3. 基础版存在的问题
    • 4. 工业级方案:Redisson分布式锁
      • 4.1 Redisson快速入门
      • 4.2 核心特性
    • 5. Redisson源码深度剖析
      • 5.1 加锁核心Lua脚本
      • 5.2 加锁流程图
      • 5.3 看门狗机制(自动续期)
      • 5.4 可重入实现原理
    • 6. 分布式锁的扩展形态
      • 6.1 读写锁(ReadWriteLock)
      • 6.2 公平锁(FairLock)
      • 6.3 红锁(RedLock)
    • 7. 最佳实践与避坑指南
      • 7.1 合理设置过期时间
      • 7.2 务必在finally中释放锁
      • 7.3 监控与告警
    • 8. 总结对比
      • 核心观点

🌺The Begin🌺点点关注,收藏不迷路🌺

引言

在分布式系统中,多个服务节点同时操作共享资源时,传统的单机锁(如synchronizedReentrantLock)就失效了。如何保证同一时刻只有一个节点能执行关键代码?分布式锁就是解决方案。

Redis凭借其高性能和原子操作,成为实现分布式锁的首选工具。本文将深入剖析Redis分布式锁的实现原理、常见坑点以及工业级解决方案Redisson的源码级解析。

1. 分布式锁的核心要求

在设计分布式锁时,必须满足以下四个条件:

要求说明
互斥性同一时刻,只有一个客户端能持有锁
死锁预防持有锁的客户端崩溃或网络异常,锁能自动释放
容错性大多数Redis节点正常运行时,客户端仍可加解锁
解铃还须系铃人加锁和解锁必须是同一个客户端,不能释放别人的锁

2. 基于Redis的简易分布式锁实现

2.1 第一阶段:SETNX + EXPIRE(有问题!)

最原始的想法:用SETNX加锁,再用EXPIRE设过期时间。

# 1. 加锁SETNX lock_key1# 2. 设置过期时间(防止死锁)EXPIRE lock_key30

问题所在SETNXEXPIRE是两个命令,非原子性。如果SETNX成功后客户端崩溃,EXPIRE没执行,就导致死锁

2.2 第二阶段:SET原子操作(正确基础版)

Redis 2.6.12+提供了SET命令的扩展参数,解决了原子性问题:

SET lock_key unique_value NX PX30000

参数说明:

  • NX:只有key不存在时才设置(实现互斥)
  • PX 30000:设置过期时间30秒(防止死锁)
  • unique_value:客户端唯一ID(如UUID),用于安全释放锁

2.3 第三阶段:安全解锁(Lua脚本)

为什么释放锁要用Lua脚本?因为要确保当前客户端只能释放自己加的锁,不能误删别人的锁。

-- 解锁Lua脚本ifredis.call("get",KEYS[1])==ARGV[1]thenreturnredis.call("del",KEYS[1])elsereturn0end

2.4 完整基础代码示例(Java版)

publicclassRedisDistributedLock{privateJedisjedis;privateStringlockKey;privateStringuniqueValue;privateintexpireTime;// 毫秒publicbooleantryLock(){// SET key value NX PX expireTimeStringresult=jedis.set(lockKey,uniqueValue,"NX","PX",expireTime);return"OK".equals(result);}publicbooleanunlock(){StringluaScript="if redis.call('get', KEYS[1]) == ARGV[1] then "+" return redis.call('del', KEYS[1]) "+"else "+" return 0 "+"end";Objectresult=jedis.eval(luaScript,Collections.singletonList(lockKey),Collections.singletonList(uniqueValue));returnLong.valueOf(1).equals(result);}}

2.5 基础版流程图

RedisClientBClientARedisClientBClientA业务执行完成SET lock UUID_A NX PX 30000OK (加锁成功)SET lock UUID_B NX PX 30000(nil) 加锁失败执行业务逻辑...EVAL 解锁脚本 (UUID_A)检查value=UUID_Adel成功SET lock UUID_B NX PX 30000OK (加锁成功)

3. 基础版存在的问题

上述实现虽然正确,但在生产环境中仍有几个痛点:

  1. 锁超时释放:业务执行时间超过过期时间,锁自动释放,导致并发问题
  2. 不可重入:同一线程无法多次获取同一把锁
  3. 不支持等待:获取不到锁直接返回失败,没有阻塞等待机制
  4. 无续期机制:无法在业务执行过程中延长锁的有效期

4. 工业级方案:Redisson分布式锁

Redisson是Redis官方推荐的Java分布式锁实现,完美解决了上述所有问题。

4.1 Redisson快速入门

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.20.0</version></dependency>
@ConfigurationpublicclassRedissonConfig{@BeanpublicRedissonClientredissonClient(){Configconfig=newConfig();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456").setConnectionPoolSize(10);returnRedisson.create(config);}}@ServicepublicclassOrderService{@AutowiredprivateRedissonClientredissonClient;publicvoidcreateOrder(LongorderId){// 1. 获取分布式锁RLocklock=redissonClient.getLock("order:"+orderId);try{// 2. 加锁(支持自动续期)lock.lock(30,TimeUnit.SECONDS);// 3. 执行业务逻辑doBusiness();}finally{// 4. 释放锁lock.unlock();}}}

4.2 核心特性

特性说明
可重入同一线程可多次获取同一把锁,内部计数器维护
自动续期看门狗机制,业务未完成自动延长过期时间
阻塞等待支持tryLock带超时时间的等待
公平锁支持FIFO顺序获取锁
读写锁读读共享、读写互斥、写写互斥

5. Redisson源码深度剖析

5.1 加锁核心Lua脚本

Redisson的加锁逻辑通过Lua脚本实现原子性,源码如下:

<T>RFuture<T>tryLockInnerAsync(longleaseTime,TimeUnitunit,longthreadId,RedisStrictCommand<T>command){returncommandExecutor.evalWriteAsync(getName(),LongCodec.INSTANCE,command,// 1. 判断锁是否存在"if (redis.call('exists', KEYS[1]) == 0) then "+"redis.call('hincrby', KEYS[1], ARGV[2], 1); "+"redis.call('pexpire', KEYS[1], ARGV[1]); "+"return nil; "+"end; "+// 2. 判断是否是当前线程持有"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then "+"redis.call('hincrby', KEYS[1], ARGV[2], 1); "+"redis.call('pexpire', KEYS[1], ARGV[1]); "+"return nil; "+"end; "+// 3. 其他线程持有,返回剩余过期时间"return redis.call('pttl', KEYS[1]);",Collections.singletonList(getName()),unit.toMillis(leaseTime),getLockName(threadId));}

数据结构说明:Redisson使用Hash结构存储锁信息

  • key: 锁名称(如"order:123")
  • field: 线程标识(如"UUID:threadId")
  • value: 重入计数

5.2 加锁流程图

收到信号

调用lock方法

执行Lua加锁脚本

锁是否存在?

创建Hash,重入计数=1,设置过期时间

是否是当前线程?

重入计数+1,重置过期时间

返回锁剩余过期时间

加锁成功

订阅锁释放Channel

等待锁释放信号

启动看门狗

定期续期

5.3 看门狗机制(自动续期)

看门狗是Redisson最核心的特性,解决了业务执行超时导致锁自动释放的问题。

privatevoidscheduleExpirationRenewal(longthreadId){ExpirationEntryentry=newExpirationEntry();EXPIRATION_RENEWAL_MAP.put(getEntryName(),entry);// 定时任务,每10秒执行一次Timeouttask=commandExecutor.getConnectionManager().newTimeout(newTimerTask(){@Overridepublicvoidrun(Timeouttimeout)throwsException{// 续期Lua脚本:重置锁过期时间为30秒RFuture<Boolean>future=renewExpirationAsync(threadId);future.onComplete((res,e)->{if(res){// 续期成功,递归调用继续续期scheduleExpirationRenewal(threadId);}});}},internalLockLeaseTime/3,TimeUnit.MILLISECONDS);// 默认30秒/3=10秒执行一次entry.setTimeout(task);}

看门狗工作流程

  1. 客户端加锁成功,默认过期时间30秒
  2. 启动后台线程,每10秒检查一次
  3. 如果锁仍被当前线程持有,执行续期Lua脚本,重置过期时间为30秒
  4. 业务执行完毕,主动释放锁,取消看门狗
  5. 如果客户端崩溃,看门狗停止续期,锁30秒后自动释放

5.4 可重入实现原理

通过Hash结构的计数器实现可重入:

# 第一次加锁HSET"order:123""clientA:thread1"1PEXPIRE"order:123"30000# 同一线程再次加锁HINCRBY"order:123""clientA:thread1"1# 计数变为2PEXPIRE"order:123"30000# 重置过期时间# 释放锁HINCRBY"order:123""clientA:thread1"-1# 计数减1# 计数>0,保留锁# 计数=0,删除key

6. 分布式锁的扩展形态

Redisson还提供了多种高级锁类型:

6.1 读写锁(ReadWriteLock)

适用于读多写少的场景,提升并发性能。

RReadWriteLockrwLock=redissonClient.getReadWriteLock("data-lock");RLockreadLock=rwLock.readLock();// 读锁RLockwriteLock=rwLock.writeLock();// 写锁// 读锁可被多个线程同时持有readLock.lock();// 写锁独占writeLock.lock();

6.2 公平锁(FairLock)

保证先等待的线程先获得锁。

RLockfairLock=redissonClient.getFairLock("fair-lock");fairLock.lock();// 按请求顺序分配

6.3 红锁(RedLock)

多Redis节点部署时的高可用方案,需要大多数节点同意才能加锁。

7. 最佳实践与避坑指南

7.1 合理设置过期时间

// 错误:过期时间太短,业务可能未完成lock.lock(5,TimeUnit.SECONDS);// 正确:使用默认30秒,配合看门狗自动续期lock.lock();// 或者根据业务评估设置足够长时间lock.lock(60,TimeUnit.SECONDS);

7.2 务必在finally中释放锁

RLocklock=redissonClient.getLock(key);lock.lock();try{// 业务逻辑}finally{// 确保一定释放if(lock.isLocked()&&lock.isHeldByCurrentThread()){lock.unlock();}}

7.3 监控与告警

@ComponentpublicclassLockMonitor{privatestaticfinalMeterRegistryregistry=newSimpleMeterRegistry();// 监控锁等待时间privateTimerlockWaitTimer=Timer.builder("lock.wait.time").register(registry);// 监控锁持有时间privateTimerlockHoldTimer=Timer.builder("lock.hold.time").register(registry);public<T>TexecuteWithMonitor(StringlockKey,Supplier<T>supplier){RLocklock=redissonClient.getLock(lockKey);longstartWait=System.currentTimeMillis();lock.lock();longwaitTime=System.currentTimeMillis()-startWait;lockWaitTimer.record(waitTime,TimeUnit.MILLISECONDS);longstartHold=System.currentTimeMillis();try{returnsupplier.get();}finally{lock.unlock();longholdTime=System.currentTimeMillis()-startHold;lockHoldTimer.record(holdTime,TimeUnit.MILLISECONDS);// 告警检查if(holdTime>10000){// 持有超过10秒alertService.sendAlert("锁持有时间过长: "+lockKey);}}}}

8. 总结对比

方案优点缺点适用场景
SETNX基础版实现简单无续期、不可重入学习测试
手动Lua脚本原子操作功能单一简单场景
Redisson功能完善、自动续期、可重入引入依赖生产环境首选

核心观点

  1. 原子性是基础:加解锁必须用Lua或原子命令
  2. 唯一标识是关键:用UUID标识锁持有者,防止误删
  3. 看门狗是保障:自动续期解决业务超时问题
  4. 选型要慎重:生产环境直接用Redisson,不要重复造轮子


🌺The End🌺点点关注,收藏不迷路🌺
http://www.jsqmd.com/news/402525/

相关文章:

  • 改稿速度拉满!专科生专属降AI神器 —— 千笔AI
  • 智能语音客服与RAG技术融合:从架构设计到生产环境实践
  • ChatGPT文献阅读效率提升实战:从文本解析到知识提取的最佳实践
  • 真的太省时间!千笔ai写作,专科生论文好帮手
  • 2026冲刺用!千笔写作工具,最受欢迎的一键生成论文工具
  • 2026年辽宁汽车增压器改装趋势,荣威车主口碑之选,北汽2.0增压器/江雁增压器/三菱奕歌增压器,汽车增压器改装哪个好 - 品牌推荐师
  • OpenCV毕设新手避坑指南:从环境配置到第一个图像处理应用
  • Spokenly语音输入配置Qwen3-asr进行语音输入
  • AI元人文对“大模型赋能哲学社会科学研究”三个深化维度的回应
  • 如何为CLine选择火山方舟API Provider:技术选型与实战指南
  • 北京朝阳区附近回收黄金店实测,我把几家都跑了一遍
  • 运筹学-指派问题(匈牙利法)
  • C++11现代化编程基础
  • Chatbot Arena实战:基于人类偏好的LLM评估平台开发指南
  • 兰亭妙微作品一龙泉茶苑麻将游戏界面设计
  • CosyVoice 报错 ‘没有预训练音色‘ 的深度解析与解决方案
  • 解决 ‘chattts/asset/decoder.safetensors not exist‘ 错误的AI辅助开发实战指南
  • cnPack里MarkDown里RTF显示
  • 基于MCP的智能客服系统开发实战:知识库与工单系统深度集成方案
  • ChatTTS 实战教程:从零构建高自然度语音合成系统
  • CentOS 7/8 系统下 FunASR 语音识别引擎的完整部署指南与性能调优
  • 实战解析:如何通过CosyVoice API构建高可用有声内容生成系统
  • ChatTTS模型实战指南:从零搭建到生产环境部署的避坑要点
  • 深入解析cosyvoice 3.0开源框架:技术选型与生产环境实践指南
  • ChatTTS中Speaker Embedding乱码问题:原理分析与解决方案
  • 兰亭妙微作品一江苏锐创数据中心监控大屏交互及视觉设计
  • 写作压力小了,AI论文网站 千笔·专业论文写作工具 VS 学术猹,专科生专属!
  • Claude Code官方Prompt XML解析:AI辅助开发中的高效实践与避坑指南
  • 2026年市场上优质的抖音矩阵企业哪家好,视频矩阵/抖音代运营/广告代运营/微信朋友圈广告,抖音矩阵公司推荐排行 - 品牌推荐师
  • Unity游戏毕业设计论文技术指南:从架构设计到性能优化的完整实践