在分布式场景中,也需要“抢占”的逻辑,可以用 Redis 的setnx实现:

package main

import (
    "github.com/go-redis/redis"
)

func setnx() {
    client := redis.NewClient(&redis.Options{})

    var lockKey = "counter_lock"
    var counterKey = "counter"

    // lock
    resp := client.SetNX(lockKey, 1, time.Second*5)
    lockStatus, err := resp.Result()
    if err != nil || !lockStatus {
        println("lock failed")
        return
    }

    // counter++
    getResp := client.Get(counterKey)
    cntValue, err := getResp.Int64()
    if err == nil || err == redis.Nil {
        cntValue++
        resp := client.Set(counterKey, cntValue, 0)
        _, err := resp.Result()
        if err != nil {
            println(err)
        }
    }
    println("current counter is ", cntValue)

    // unlock
    delResp := client.Del(lockKey)
    unlockStatus, err := delResp.Result()
    if err == nil && unlockStatus > 0 {
        println("unlock success")
    } else {
        println("unlock failed", err)
    }
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            setnx()
        }()
    }
    wg.Wait()
}

运行结果:

➜  go run main.go
lock failed
lock failed
lock failed
lock failed
lock failed
lock failed
lock failed
lock failed
current counter is  34
lock failed
unlock success

通过代码和执行结果可以看到,远程调用setnx运行流程上和单机的 trolock非常相似,如果获取锁失败,那么相关的任务逻辑就不会继续向后执行。

setnx很适合高并发场景下用来争抢一些“唯一”的资源。比如交易摄合系统中卖家发起订单,多个买家会对其进行并发争抢。这种场景我们没有办法依赖具体的时间来判断先后,因为不同设备的时间不能保证使用的是统一的时间,也就不能保证时序。

所以,我们需要依赖于这些请求到达redis 节点的顺序来做正确的抢锁操作。

如果用户的网络环境比较差,是必然抢不到的。

最后编辑: kuteng  文档更新时间: 2022-03-22 19:29   作者:kuteng