實現一個redis的分散式鎖

fireqong發表於2021-12-22

分散式鎖的實現有挺多細節要注意。
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,避免記憶體洩漏。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章