Redis单分片集群中使用SET NX实现分布式锁时多goroutine重复获锁的问题咨询
嘿,这个问题确实有点反常识,毕竟SET NX的核心逻辑就是同一时间只能有一个客户端拿到锁对吧?我帮你梳理几个最可能的原因,你可以逐一排查:
1. 从节点可写+客户端路由错误(最常见的坑)
你说集群是单分片3节点,正常应该是1主2从的结构,从节点默认是只读的。但如果有人修改了Redis配置,把从节点的slave-read-only(新版本是replica-read-only)改成了no,同时你的go-redis客户端(尤其是Cluster模式的客户端)没有正确配置路由策略,导致部分goroutine的请求被发到了从节点上。
主从同步是异步的,当第一个goroutine在主节点SET成功后,第二个goroutine的请求发到从节点时,从节点上还没有这个key,所以SET NX会返回true,自然就拿到锁了。你可以先去检查下从节点的只读配置,再确认下客户端是不是只把写请求发往主节点。
2. go-redis客户端的配置或实例问题
如果你开启了客户端的自动重试(比如MaxRetries大于0),而且刚好遇到了主节点的短暂网络波动,第一个goroutine的请求其实已经在主节点成功了,但客户端没收到响应触发了重试——不过这种情况重试请求应该会返回false,不太会导致两个成功。但如果你的代码里RedisClient不是全局共享的单一实例,而是每个goroutine都初始化了一个新实例,其中某个实例的路由逻辑出问题,也可能出现这种情况。你可以检查下RedisClient的初始化和复用逻辑。
3. 代码里的笔误或key一致性问题
看你贴的代码,lockHelper里调用FetchLock的时候传了三个参数,但你定义的FetchLock函数只有两个参数,这明显是粘贴时的笔误吧?会不会实际代码里FetchLock的参数有问题,或者key的生成逻辑有漏洞?比如有没有可能不同goroutine用的key其实不一样?你可以在代码里把实际使用的key打印出来,确保所有goroutine抢的是同一个key。
4. Redis集群分片配置错误
虽然你说这是单分片集群,但会不会实际配置出了问题——比如三个节点都是主节点,各自负责一部分哈希槽?这种情况下,如果你用的key的哈希槽被分配到了两个不同的主节点,那两个goroutine的请求会发到不同的主节点,自然都能拿到锁。你可以用redis-cli cluster keyslot {keyId1}命令查下这个key对应的哈希槽,再确认这个哈希槽只属于一个主节点。
最后给你个快速排查的小技巧:在Redis上执行MONITOR命令,然后跑你的测试,这样就能抓到所有goroutine发的实际命令,包括目标节点、命令内容和返回结果,一下子就能定位到问题根源。
内容来源于stack exchange

