初次见到这道题题时候感觉很奇怪,心里默想这两家伙不是一样的吗,但是既然这样问,以及有这样的题目,那就让我们来了解一下他吧。
寻找了答案之后是这样回答的
在 Redis 中,
SET NX
和SETNX
的功能是相同的,都用于在键不存在时设置键值对,但它们存在以下细微区别:
1、命令格式
SETNX key value
这是一个原子命令,格式固定为SETNX key value
,不可添加其他参数。SET key value NX
这是SET
命令的扩展用法,通过添加NX
选项来实现与SETNX
相同的效果。SET
命令还支持其他选项(如EX
、PX
、XX
等),使其功能更灵活。
2、原子性
两者都是原子操作,在分布式环境中不会出现竞态条件。例如:
SETNX mykey "value" # 原子操作,等价于:
SET mykey "value" NX # 同样是原子操作
3、返回值
-
SETNX
- 成功(键不存在):返回
1
- 失败(键已存在):返回
0
- 成功(键不存在):返回
-
SET key value NX
- 成功(键不存在):返回
OK
- 失败(键已存在):返回
nil
- 成功(键不存在):返回
4、兼容性
SETNX
是 Redis 早期版本就支持的命令,所有 Redis 版本都兼容。SET ... NX
是在 Redis 2.6.12 版本后引入的扩展语法,老版本可能不支持。
5、github.com/redis/go-redis/v9包小探索
在此包中只有SETNX方法,但是当我们进入源码中可以发现SETNX将redis中得set nx 和 setnx 合二为一了:
// SetNX Redis `SET key value [expiration] NX` command.
//
// Zero expiration means the key has no expiration time.
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
// otherwise you will receive an error: (error) ERR syntax error.
func (c cmdable) SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd {
var cmd *BoolCmd
switch expiration {
case 0:
// Use old `SETNX` to support old Redis versions.
cmd = NewBoolCmd(ctx, "setnx", key, value)
case KeepTTL:
cmd = NewBoolCmd(ctx, "set", key, value, "keepttl", "nx")
default:
if usePrecise(expiration) {
cmd = NewBoolCmd(ctx, "set", key, value, "px", formatMs(ctx, expiration), "nx")
} else {
cmd = NewBoolCmd(ctx, "set", key, value, "ex", formatSec(ctx, expiration), "nx")
}
}
_ = c(ctx, cmd)
return cmd
}
const KeepTTL = -1
func usePrecise(dur time.Duration) bool {
return dur < time.Second || dur%time.Second != 0
}
1、过期时间为 0 :永远有效
2、过期时间为 -1:可用于续期
分布式锁续期:在延长锁的持有时间时不改变锁的值。
业务场景
- 当持有锁的业务执行失败需要retry时候,在释放锁和重新加锁时候,在检查锁归属和延长时间之间,锁可能已过期并被其他客户端获取(竞态条件)。使用它可以原子性续期
// 原子操作:仅当锁存在且值未变时延长锁时间
ok, _ := client.Set(ctx, "lock:resource", "my-value", "NX", "KEEPTTL").Result()
if ok {
// 锁续期成功,继续执行临界区代码
} else {
// 锁已过期或被其他客户端获取,需重新竞争锁
}
3、自定义过期时间:default分支,区分了毫秒级和秒级的过期时间。
6、多一嘴 过期时间为 -1
在普通的set中:过期时间为 -1时候、键存在可以用于刷新值而不改变过期时间。(用户信息刷新),不存在则创建永久的key
// 原子操作:仅当锁存在时续期(等价于原代码中的 SetNX + KeepTTL)
client.Set(ctx, "lock:resource", "my-value", "NX", redis.KeepTTL).Result()
为了避免误产生永久key
// Lua 脚本:仅当键存在时更新值并保留 TTL
script := ` if redis.call("EXISTS", KEYS[1]) == 1 then return redis.call("SET", KEYS[1], ARGV[1], "KEEPTTL") else return 0 end `
result, _ := client.Eval(ctx, script, []string{"user:1"}, "new-value").Result()
if result == "OK" { // 更新成功 } else {
// 键不存在,需创建
client.Set(ctx, "user:1", "new-value", 60*time.Second) }
总结
在使用分布式锁中,要注意时间的设置,否则可能会造成死锁。