redis分布式锁最稳妥解法是setnx配合expire原子命令及lua脚本释放,因synchronized仅限单jvm生效,无法解决微服务集群并发问题;必须用set key value ex s nx保证加锁原子性,value设唯一uuid,释放锁需lua校验所有权后删除,避免误删他人锁。

直接用互斥锁(SETNX)是最稳妥的解法,但必须带超时时间、必须原子性释放、不能只靠 synchronized —— 后者在分布式环境下完全无效。
为什么 synchronized 在生产环境里根本没用
它只在单 JVM 进程内生效。微服务集群里,哪怕你加了 synchronized,10 台机器上的 10 个线程照样同时去查数据库。实际压测中,这种写法和没加锁一样。
- 本地锁无法跨进程,更不跨机器
-
volatile修饰的锁对象对其他节点毫无意义 - 即使用了 Spring 的
@Cacheable,底层仍是单机缓存,不解决分布式竞争
SETNX 加锁必须配 EXPIRE,且要原子执行
Redis 2.6.12+ 支持 SET key value EX seconds NX,这是唯一安全的起点。老版本用 SETNX + EXPIRE 有竞态风险:加锁成功后宕机,EXPIRE 没执行,锁就永远卡住。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 务必用一条命令完成“设值 + 过期 + 仅当不存在”三件事
- 过期时间建议设为业务查询耗时的 2–3 倍(比如 DB 查询平均 200ms,设 600ms),太短易误删,太长阻塞久
- 锁 key 要带业务标识,比如
lock:user:1001,避免不同 key 互相干扰
锁释放必须用 Lua 脚本校验所有权
不能简单 DEL lock:key。A 线程加锁后慢查询超时,锁自动过期;B 线程拿到锁并完成重建;此时 A 醒来直接 DEL,就把 B 的锁给删了 —— 典型的“误删他人锁”。
- 释放锁必须先
GET判断值是否匹配(即自己写的随机 token) - 这个判断 + 删除必须原子,用 Lua 脚本:
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end - 加锁时 value 必须是唯一随机字符串(如 UUID),不能是固定值
1
重建缓存失败时,锁要主动清理,空值也要缓存
DB 查询失败(网络超时、连接池满等)不能让锁一直挂着,否则后续所有请求全被堵死。同时,空结果也得缓存,否则下次请求又撞上来。
- DB 查询异常后,立刻用 Lua 脚本释放锁,再返回错误或兜底数据
- 若查到 null,仍要写入缓存,value 设为
"NULL"或自定义NullValue对象,过期时间设短(如60秒) - 不要依赖“锁自动过期”来兜底——过期前这段时间全是不可用窗口
真正难的不是写几行加锁代码,而是把锁生命周期、缓存写入、空值处理、异常回滚全部串成原子链条。漏掉任意一环,高并发下就会出现缓存未重建、锁残留、空查询反复穿透等问题。










