谈谈Redis中的SetNX

2020年9月19日 0 条评论 3.55k 次阅读 0 人点赞

谈SetNX命令前,先顺带引入下Set命令,由于在Golang开启两个并发协程后,单位时间内读到的有可能是同一个值,因此这对本来就是单线程并发安全的Redis造成了非并发安全的错觉。如下代码所示:

其实redis本身是并发安全的。只是单位时间有两个协程同时读到了一样的值

下面引入SETNX

在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

当我们在两个终端执行命令:

set lock 123 nx ex 60

如下所示:

有且只有左边的终端执行成功了, setnx 命令是原子性的,因此在并发情况下,如果某个进程或者线程在设置成功了某个值,那么就代表它获取到了锁。并且执行完自己的代码后删除该 KEY 即可。

但这样真的规范吗?

在上面的命令我设置了锁的缓存时间为 60 秒

假设我的A进程获取到了锁并开始执行业务逻辑,但由于业务繁重,执行时长超过了锁设置的缓存时间,那么其实下一个进程 B 早已获取到了该锁。然而我的A进程才执行完就误删了进程B的锁。导致C 进程也获取到了锁。

因此这种方式是不规范的。

因此我们要在创建锁的时候引入一个随机值:

当A进程获取到锁的时候顺带设置这个随机值,并且当A进程结束进程后,取出lock这个key中的随机值,看看是否是自己设置的那个,如果是则删除,如果不是就略过。这样就避免了误删的情况。

上一段 PHP 伪代码

补充:本文在删除锁的时候,实际上是有问题的,没有考虑到 GC pause 之类的问题造成的影响,比如 A 请求在 DEL 之前卡住了,然后锁过期了,这时候 B 请求又成功获取到了锁,此时 A 请求缓过来了,就会 DEL 掉 B 请求创建的锁,此问题远比想象的要复杂,具体解决方案参见本文最后关于锁的若干个参考链接。

如此基本实现了单机锁,假如要实现分布锁,请参考:Distributed locks with Redis,不过分布式锁需要注意的地方更多:How to do distributed lockingIs Redlock safe。此外,还有中文版:基于Redis的分布式锁到底安全吗()。

兰陵美酒郁金香

大道至简 Simplicity is the ultimate form of sophistication.

文章评论(0)

你必须 登录 才能发表评论