分散式鎖的實現有挺多細節要注意。
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 協議》,轉載必須註明作者和本文連結