SET key value NX EX 是分布式锁起点,因其原子性确保“仅当键不存在时设值+设过期”一步完成,避免拆分操作导致的死锁或误删;值须为唯一UUID,解锁须用Lua脚本校验value后删除,保障安全性。

为什么 SET key value NX EX 是分布式锁的起点
因为 Redis 的原子性命令是唯一能避免竞态的基石。用 SET 同时完成“设置值 + 设置过期 + 仅当不存在时生效”,三者缺一不可。如果拆成 EXISTS + SET + EXPIRE,中间任何一步失败或被中断,都会导致锁残留或误释放。
实际写法必须是:
SET lock:order:123 "8f4a1e7b-2c9d-4a5f-b6e0-3a1d2c9b8e7f" NX EX 30
其中值必须是全局唯一随机字符串(比如 UUID),不能用进程 PID 或时间戳——否则多个客户端可能生成相同值,后续无法安全校验和释放。
- 值重复会导致误删别人持有的锁,这是最常见也最危险的错误
-
NX保证只在 key 不存在时设置,避免覆盖已有锁 -
EX 30必须显式指定,不能依赖客户端延时续期——网络抖动时续期可能失败,而无过期时间的锁等于死锁
如何安全释放锁:为什么不能直接 DEL
直接 DEL lock:order:123 会删掉别人刚抢到的锁。必须先校验 value 是否匹配,再删除——但 Redis 没有内置的“校验后删”原子命令,所以要用 Lua 脚本。
标准释放脚本长这样:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
这个脚本必须通过 eval 或 evalsha 执行,且传入的 KEYS[1] 和 ARGV[1] 要严格对应锁 key 和你当初设进去的唯一 value。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 漏传
ARGV[1]或拼错变量名,脚本永远返回 0,锁永久残留 - 用 Go 的
redis.UniversalClient时,推荐用script.Load().Run()方式预加载脚本,避免每次传输 Lua 字符串开销 - 别试图用
GET + if + DEL三步模拟——两步之间可能被其他客户端抢占
Go 客户端怎么封装才不容易出错
核心是把“获取锁”和“释放锁”绑定为同一实例生命周期,避免 value 不一致。不要让调用方自己生成 token 或传入 key。
一个最小可行封装示例:
type RedisLock struct {
client redis.Cmdable
key string
token string // 初始化时生成,只读
expire time.Duration
}
func (l *RedisLock) TryLock(ctx context.Context) (bool, error) {
ok, err := l.client.SetNX(ctx, l.key, l.token, l.expire).Result()
return ok, err
}
func (l *RedisLock) Unlock(ctx context.Context) error {
_, err := l.script.Run(ctx, l.client, []string{l.key}, l.token).Result()
return err
}
关键点:
-
token在NewRedisLock()里一次性生成并固定,不暴露给外部 -
TryLock()返回bool而非 error,因为失败是常态,不是异常 - 没加自动续期(renew)逻辑——除非业务明确需要长任务,否则续期引入更多复杂度(比如 watch dog 线程、心跳竞争),多数场景 30 秒够用,超时就重试更简单
Redlock 真有必要吗?大多数项目该用单节点还是集群锁
Redis 官方已不推荐 Redlock,因为其“时钟漂移容忍”假设在真实网络中很难满足。如果你的 Redis 是主从架构(非 Redis Cluster),且部署在同一个机房,用单节点 SET NX EX 完全够用——主节点宕机时锁丢失,但业务通常也能接受短暂的重复执行(配合幂等设计)。
只有当你同时满足以下三点,才考虑 Redlock 或类似多节点方案:
- Redis 部署跨多个可用区(比如 AWS 三个 AZ)
- 业务对“锁绝对不丢失”有强要求(例如金融级资金扣减)
- 愿意承担 Redlock 带来的延迟上升(需访问多数节点)和实现复杂度
否则,与其花精力搞 Redlock,不如把精力放在:锁 key 的命名规范(含业务标识+ID)、value 的唯一性保障、释放失败的日志告警、以及最关键的——所有临界区代码都做幂等处理。

















