尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

SpringBoot整合Redis分布式锁-为什么不能直接DEL

SpringBoot整合Redis分布式锁-为什么不能直接DEL Redis 分布式锁为什么不能直接 DEL锁归属与 Lua 原子释放工程判断Redis 分布式锁最危险的代码通常不是加锁而是 finally 里的 DEL。线程持有的锁过期后另一个线程可能已经拿到同名锁此时前一个线程直接删除就会误删别人的所有权。一个可靠的锁至少要绑定持有者标识并让“判断所有者”和“删除 Key”保持原子还要明确租期、续期、可重入和业务执行超时。简单 setnx 加 del 只覆盖了演示场景。MetaLite 的 Redis 封装把锁的所有权校验和释放语义收敛到公共能力避免业务重复实现危险版本。本文先复现误删时序再结合 RedisClient 的实现说明这层浅封装为何具有工程价值。一、锁值为什么必须每次唯一MetaLite 获取锁时生成一个唯一lockValueStringlockValueFastUniqueId.getNumber();BooleanresultredisClient.vSetIfAbsent(lockKey,lockValue,expireMillis,TimeUnit.MILLISECONDS);成功后调用方同时持有lockKey → 锁定哪个资源 lockValue → 这次锁的所有者凭证同一个服务实例先后两次获取同一 Key也应该拥有不同 Value。因为锁的所有权属于“一次获取”不是某个进程永久拥有。二、比较和删除为什么必须原子执行如果 Java 代码先GET比较再执行DEL两条命令之间仍有竞争窗口GET 发现还是自己的值 ↓ 锁恰好过期其他实例获得新锁 ↓ DEL 删除新锁MetaLite 使用 Lua 把判断与删除放进 Redis 一次执行ifredis.call(get,KEYS[1])ARGV[1]thenreturnredis.call(del,KEYS[1])elsereturn0end只有 Redis 中的值仍等于本次lockValue才允许删除。这条脚本解决的是“误释放其他所有者的锁”不代表业务操作与 Redis 锁获得了数据库事务级原子性。三、过期时间为什么不可省略如果持锁进程崩溃、机器断电或网络中断正常解锁代码不会执行。没有 TTL 的锁会永久存在后续请求全部无法进入。当前实现要求过期时间大于 1 秒 小于 24 小时 默认 5 秒TTL 是防止死锁的最后保障但也引入新的问题业务执行时间超过 TTL 时锁会提前失效。因此过期时间不是越短越安全。它至少要覆盖正常执行时长、可接受抖动和基础设施延迟并配合超时监控。四、自动续期怎样确认自己仍是所有者开启autoRenewal后MetaLite 按过期时间的 30% 调度续期任务。续期同样使用 Luaifredis.call(get,KEYS[1])ARGV[1]thenreturnredis.call(expire,KEYS[1],ARGV[2])elsereturn0end它先验证锁值再延长 TTL。若 Key 已不存在或值已变化返回 0 并停止当前续期任务。这避免旧所有者为新所有者的锁续命。五、当前自动续期不是无限 watchdog“自动续期”容易让人理解为业务不结束锁就一直续。当前实现不是这样。最大续期次数由下面的计算得到intmaxRenewalCount(int)(1/RENEWAL_INTERVAL_RATIO)1;比例为 0.3 时得到 4任务在 finally 中先加次数再以判断取消因此实际还会经历一次边界续期。无论如何它是有上限的定期续期不是随业务执行时长无限延续的 watchdog。如果任务可能运行远超锁时间不能只看autoRenewaltrue就认为互斥一定覆盖完整执行期。需要根据代码精确计算最长保护窗口或把续期改成“持锁且业务未结束就继续”的生命周期模型同时设置总时长保护。六、毫秒 TTL 为什么在续期时变成了秒首次加锁使用毫秒单位。续期脚本却调用 RedisEXPIRE并传入expireMillis/1000这会截断不足一秒的部分。例如 1500 毫秒续期后会成为 1 秒而不是 1.5 秒。当前参数只要求大于 1 秒并没有要求必须是 1000 的整数倍。如果希望续期保持与首次加锁一致的精度更合适的是使用PEXPIRE并直接传毫秒值。这是典型的单位边界配置字段写着毫秒不代表整条执行链都保持毫秒精度。七、注解怎样把锁与业务方法连接起来MetaLite 提供RedisLockRedisLock(keylock:api:order: #orderId,expireMillis10000,autoRenewaltrue)publicResp?submit(StringorderId){// 业务逻辑}Key 可以是常量也可以通过 SpEL 从方法参数计算。切面拿到锁后会把 Key、Value、过期时间和续期标记写进AspectInfo。方法结束后再从同一上下文解锁。这种方式减少业务代码的 try/finally但锁粒度仍由业务设计者负责。把所有订单共用一个 Key 会造成不必要串行只用用户 ID 又可能无法保护具体订单资源。八、异常时锁会不会释放入口切面的通用逻辑在finally中执行所有 Handler 的postHandle。SpringScheduledJobExecuteHandler.postHandle会调用解锁因此业务方法抛出异常时也会进入释放路径。如果 Redis 解锁失败TTL 仍是最终兜底。自动续期任务只有解锁成功才立即取消锁已过期或所有权变化时旧续期任务会依靠 Lua 比较失败或次数上限停止。这说明分布式锁需要多重退出路径正常 finally 解锁 所有权校验失败不误删 TTL 自动过期 续期任务检测失效后停止九、锁只能提供互斥不能替代幂等即使锁实现完全正确仍可能发生锁过期后旧任务继续执行Redis 主从切换带来时序窗口业务已提交但响应丢失导致调用方重试续期线程长时间停顿数据库操作与解锁之间进程崩溃。关键写操作仍应设计业务幂等例如唯一约束、状态机、请求号或版本号。分布式锁降低并发进入概率业务幂等负责即使重复进入也不产生错误结果。两者不是替代关系。十、分布式锁要监控什么至少应观察获取成功率和等待/失败次数实际执行时间与配置 TTL 的分布续期失败次数解锁失败次数达到最大续期次数的锁热点 Key 和持锁最长任务。MetaLite 当前会在执行超过配置时间、续期失败和达到次数上限时记录日志为发现配置不合理提供线索。正确的 Redis 锁不是SET NX加一个DEL。它必须把所有权、原子释放、过期、续期和业务幂等放在同一条故障时间线上理解。框架简介MetaLite 是面向企业生产环境的新一代 Java 微服务技术底座。系列文章重点分享代码背后的设计思路、技术取舍与工程实践。源码基线JDK 21、Spring Boot 3.2.9、Spring Cloud 2023.0.1、Spring Cloud Alibaba 2023.0.1.3具体组件版本以项目backend-bom为准。作者简介15 年 Spring 体系企业级开发经验专注于 Java 微服务架构、工程治理与生产实践。持续更新MetaLite 系列内容将持续更新围绕核心设计、源码链路、技术取舍与生产实践展开。欢迎关注作者及时获取后续内容。在线演示演示地址: https://admin.metalite.top/演示账号: guess演示密码: admin2026
返回列表