关于redis分布式锁的问题. 如果没抢到锁,那么应该怎么做,一直重复抢锁的操作吗?

啊哦
  • 723

新手初学redis,尝试redis锁的时候,go代码如下:

func main() {
    client := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
    for i := 0; i < 100; i++ {
        go func() {
            for {
                //一直 for 循环抢锁吗?
                ok, err := client.SetNX("sync", true, time.Second*5).Result()
                if err == nil && ok {
                    res, err := client.Get("num").Result()
                    fmt.Printf("num : %s\n", res)
                    num, err := strconv.Atoi(res)
                    err = client.Set("num", num+1, 0).Err()
                    _, err = client.Del("sync").Result()
                    if err != nil {
                        log.Println(err)
                    }
                    break
                }
                //time.Sleep(time.Millisecond * 10)
            }
        }()
    }
    time.Sleep(time.Second * 20)
}

代码中我开了 100 个协程抢锁,操作一个数据.
我的疑问是,当一个协程抢到了这个 sync 锁,那么其他的 99 个协程, 都一直在不停的在一个循环中重复抢锁吗?这样子是否会对redis产生大量的无意义的请求...网上搜了一下相关的实现demo代码,发现都是类似的

while(true)
{
    // xxxxxxx
}

进行抢锁....所以很疑惑,若是真实生产环境也是这样实现嘛....还是我一开始就错了...如果有相关资料或者是搜索关键词,还望大佬指点一二.

回复
阅读 3.9k
5 个回答
临风
  • 1.3k
✓ 已被采纳

看你要实现什么样的锁,或者基于什么样的使用场景,比如说排它锁redis就实现不了,排它锁在获取不到锁的情况下会阻塞进入等待队列。在其他进程释放锁时会通知该进程再去获取锁,redis不提供这种基于key的通知机制,所以他实现不了排它锁。不过分布式排它锁,可以由Zookeeper实现。

另外一种分布式锁的实现方式是通过乐观锁和自旋的实现方式,就是你说的那种方式,不过一般会设置超时时间,也就是redis设置keyttl。这样不至于进程一直等待下去。这种锁适应于锁内操作时间比较短,锁竞争不是那么激烈的情况。

你说的那种100个进程抢锁的情况,竞争这么激烈,还是用排它锁比较好。

这个原因充分说明了要项目驱动学习。
只有你知道具体的项目的场景,才可以决定自己这个锁怎么处理。
比如,你如果抢不到资源就必须等待,而且是同步请求,那么必须等待。
比如,你如果可以接受若一致,就可以考虑等待多久锁,然后放弃,记录日志或者其他补偿机制。
当然这个就类似于 ReentrantLock 里面的 tryLock 和 tryLock(long timeout, TimeUnit unit) 的区别

需要带锁的操作一遍不会这样的逻辑。。。
比如缓存数据的更新。

1.取到锁的人去查数据库并更新数据
2.未取到锁的要么直接返回空,要么就是要设置二级缓存,把二级缓存的数据返回。。

大家都一直要拿到同一个锁,应该没有这种操作吧?

具体还是要有业务逻辑来进行代码编写,不然没有意义

糖杀西红柿
  • 3
新手上路,请多包涵

楼主现在有新的想法吗?我也很好奇,感觉一直查询redis,看看锁是否被删除,性能很低啊。

entere
  • 3
新手上路,请多包涵
if err == nil && ok {
    res, err := client.Get("num").Result()
    fmt.Printf("num : %s\n", res)
    num, err := strconv.Atoi(res)
    err = client.Set("num", num+1, 0).Err()
    _, err = client.Del("sync").Result()
    if err != nil {
        log.Println(err)
    }
    break
} else {
    // 这个目的就是上面的if操作结果
    if 目的达到 {
        break
    }
    if 达到最大重试次数 {
        break
    }
    
    if 达到最大限制时间 {
        break
    }
}
                

不知道这样是否可以

宣传栏