分布式锁的实现
package main
//使用 GitHub开源的分布式锁库
import (
"context"
"errors"
"github.com/bsm/redislock"
"github.com/go-redis/redis/v8"
"log"
"time"
//"log"
)
var (
//获取锁时间超时
WaitLockTimeout = errors.New("wait for lock timeout")
)
type WatchDogLock struct {
WaitLockTime time.Duration // 等待锁的时间,超时自动退出
KeyTTL time.Duration // key的超时时间
LockKey string //lock key
realLock *redislock.Lock //实现的类库
//closeSignal context.Context // 用来判断是否关闭锁了
client *redis.Client
// for cancel
cancelWatchDog func()
//ctx context.Context
}
func NewWatchDogLock(waitLockTime time.Duration, keyTTL time.Duration, lockKey string, cli *redis.Client) *WatchDogLock {
return &WatchDogLock{
WaitLockTime: waitLockTime,
KeyTTL: keyTTL,
LockKey: lockKey,
client: cli,
}
}
func (w *WatchDogLock) _close() error {
if w.client != nil {
defer w.client.Close()
}
//关闭看门狗
if w.cancelWatchDog != nil {
//log.Println("cancel")
w.cancelWatchDog()
}
//删除锁
if w.realLock != nil {
//log.Println("release")
err := w.realLock.Release(context.Background())
return err
}
return nil
}
func (w *WatchDogLock) TryLock() (ok bool, err error) {
locker := redislock.New(w.client)
var LockWaitTimeout = time.After(w.WaitLockTime)
//设置等待锁的时间
//ctx := context.Background()
var lock *redislock.Lock
tryLock:
for {
select {
case <-LockWaitTimeout:
log.Println("获取锁失败,已到超时时间")
return false, WaitLockTimeout
default:
lock, err = locker.Obtain(context.Background(), w.LockKey, w.KeyTTL, nil)
//无法获得锁
if err == redislock.ErrNotObtained {
time.Sleep(time.Millisecond * 600)
log.Println(" try ...")
//重试
continue tryLock
} else if err != nil {
log.Println("lock unknown status %+v", err)
return false, nil
}
//lock success
break tryLock
}
}
// 获得锁成功
if lock == nil {
log.Println("未知异常,获取锁失败 lock==nil")
return false, errors.New("无法获得锁")
}
w.realLock = lock
//自动对锁续期
var ctx, cancel = context.WithCancel(context.Background())
//w.ctx = ctx
w.cancelWatchDog = cancel
var watchDog = func() {
for {
select {
case <-ctx.Done():
log.Println("cancel watchdog")
//被cancel掉了说明任务做完,立刻退出,不要继续加时间了
return
default:
//时间不够了, 再加 4秒时间
lock.Refresh(ctx, time.Second*30, nil)
//刷新的话,最好睡一下
select {
case <-ctx.Done():
log.Println("cancel watchdog 109")
return
case <-time.After(time.Second * 10):
}
}
}
}
go watchDog()
return true, nil
//return
}
func (w *WatchDogLock) UnLock() error {
return w._close()
}
package main
import (
"github.com/go-redis/redis/v8"
"log"
"time"
)
//func GetEnv() {}
//
func worker(id int) {
var client = redis.NewClient(&redis.Options{
Network: "tcp",
Addr: "127.0.0.1:6379",
})
locker := NewWatchDogLock(time.Second*8, time.Second*50, "mylock0", client)
ok, err := locker.TryLock()
if err != nil {
if err == WaitLockTimeout {
log.Println("等待锁超时了")
}
log.Println(err)
return
}
if ok {
log.Println("=== 获取锁成功 ====",id)
time.Sleep(time.Second*2)
err = locker.UnLock()
log.Println("err info",err)
}else {
panic("获得锁失败")
}
}
func main() {
//3s 无法获得锁,就退出,不抢锁了
for i := 0; i < 2; i++ {
go worker(i)
}
time.Sleep(time.Second*40)
}