必须用唯一标识+lua脚本原子校验删除:锁value须为客户端唯一标识(如uuid),且“判断value匹配+删key”需通过lua脚本在redis端原子执行,避免直接delete导致误删他人锁。

为什么 unlock() 直接删 key 会误删他人锁
最常见错误是:获取锁时用 setIfAbsent 写入固定值(比如 "1" 或 "true"),释放锁时直接 delete("lock:order")。这看似简单,但只要两个服务实例同时竞争同一把锁,就可能出事——A 拿到锁开始执行,超时前没完成;B 在锁过期后拿到锁;A 执行完仍按原逻辑删 key,结果把 B 的锁也干掉了。
必须用唯一标识 + Lua 脚本原子校验删除
防止误删的核心就两点:锁的 value 必须是当前客户端的唯一标识(如 UUID 或线程 ID + 时间戳组合),且「判断 value 是否匹配 + 删除 key」必须在一个原子操作里完成。Redis 不支持条件删除命令,所以得靠 Lua:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
这段脚本在 Redis 服务端执行,全程无竞态。Spring Boot 中调用方式如下:
redisTemplate.execute(deleteScript, Collections.singletonList("lock:order"), "b7e2a1c9-3f5d-4b8e-9a0f-1234567890ab");
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
deleteScript是预加载的DefaultRedisScript<long></long>实例,返回类型为Long(1 表示删除成功,0 表示未删除) - 务必确保加锁时传的 value 和这里传的
ARGV[1]完全一致——不能是硬编码字符串,也不能被截断或转义 - 如果用的是 Spring Data Redis 2.7+,可复用
RedisScript工具类注册脚本,避免每次解析
value 设计不当也会导致误判
有人用 Thread.currentThread().getId() 当 value,看似唯一,但在异步线程池(如 @Async)或 WebFlux 场景下,业务逻辑可能跨线程执行,最终 unlock 时拿不到原始线程 ID。更稳妥的做法是:
- 加锁前生成一次
UUID.randomUUID().toString(),存在本地变量或ThreadLocal中 - 整个流程(加锁 → 执行 → 解锁)都使用这个值,不依赖运行时上下文
- 避免在 value 里拼接敏感信息(如用户 ID),防止日志泄露或 Redis 监控暴露业务逻辑
别忽略 Lua 脚本执行失败的兜底逻辑
即使脚本本身原子,也可能因网络中断、Redis 集群重定向、Lua 超时(默认 5 秒)而返回异常或空结果。这时候不能假设“没返回 1 就等于删成功了”。实际处理中应:
- 检查
execute()返回值是否为1L,不是则记录 warn 日志并告警,而非静默吞掉 - 绝不重试删除——重复执行 Lua 脚本不会出错,但若误判为失败而手动
delete,反而破坏原子性 - 在分布式锁工具类里统一包装异常,区分「脚本执行失败」和「value 不匹配」两类错误,前者需人工介入,后者属正常流程
真正容易被忽略的,是 value 生命周期管理——它必须严格绑定一次锁获取动作,不能复用、不能缓存、不能跨请求传递。哪怕只错一次,就可能让整个订单系统在大促时悄悄超卖。










