分布式锁的实现有挺多细节要注意。
1. 要设置过期时间,避免释放锁的时候失败了,锁长期得不到释放导致的死锁问题
2. 要设置锁的拥有者

请求一拿到锁,开始执行业务,业务执行时长超过锁设置的过期时间时,锁过期了,假设这个时候请求二拿到锁,刚开始执行业务,请求一业务执行完成,开始释放锁。因为没有设置锁的拥有者,导致请求一释放了请求二的锁,就会出现问题。

具体代码:

package redislock

import (
    "context"
    "time"

    "github.com/go-redis/redis/v8"
)

//Lock is a struct that handle config and context
type Lock struct {
    Config *Config

    context context.Context
}

//Config is a struct that maintains redis client
type Config struct {
    Client *redis.Client
}

//New is a method that return a instance of Lock
func New(c *Config) *Lock {
    return &Lock{
        Config:  c,
        context: context.Background(),
    }
}

//Get is a method that try to get distribution lock
func (l *Lock) Get(key string, ttl time.Duration, owner string) (bool, error) {
    return l.Config.Client.SetNX(l.context, key, owner, ttl).Result()
}

//Release is a method that release lock

func (l *Lock) Release(key string, owner string) (bool, error) {

 luaScript := `

if redis.call("get",KEYS[1]) == ARGV[1] then

    return redis.call("del",KEYS[1])

else

    return 0

end

`

 res, err := l.Config.Client.Eval(l.context, luaScript, []string{key}, owner).Result()

 if res.(int64) == 0 {

 return  false, err

    }

 return  true, nil

}

但仅仅是这样是不够的,因为 Get 方法只试了一次,并没有实现锁的自旋,我们应该写一个 LoopGet 方法去循环尝试获取锁。

//LoopGet is a method that try to get distribution lock looply
func (l *Lock) LoopGet(key string, ttl time.Duration, owner string) (chan bool, error) {
    c := make(chan bool, 1)

    for {
        if res, err := l.Get(key, ttl, owner); res {
            if err != nil {
                c <- false
                return c, err
            }
            c <- res
            break
        }
    }

    go func() {
        defer close(c)

        for {
            if len(c) == 0 {
                break
            }

            time.Sleep(time.Millisecond * 800)
        }
    }()

    return c, nil
}

不停地尝试获取锁,成功之后返回 channel,记得开一个协程回收 channel,当 channel 的缓冲数据被读取后,就回收该 channel,避免内存泄漏。