Geek

博客园 首页 新随笔 联系 订阅 管理

分布式锁的实现

package main

//使用 GitHub开源的分布式锁库
import (
	"context"
	"errors"
	"github.com/bsm/redislock"
	"github.com/go-redis/redis/v8"
	"log"
	"math/rand"
	"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
	//设置随机数,获取随机等待时间
	rand.Seed(time.Now().Unix())
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 {
				var v = rand.Int63n(100)
				log.Println("rand v " ,v)
				time.Sleep(time.Duration(int64(time.Millisecond) * (500 + v)))
				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",
	})
	defer client.Close()
	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)

}


posted on 2022-01-14 09:21  .geek  阅读(107)  评论(0编辑  收藏  举报