package redisLock import ( "errors" "github.com/go-redis/redis" "github.com/google/uuid" "sync" "time" ) type ( RedisLock struct { lockChan chan struct{} rwLock sync.RWMutex lockKey string //锁的key lockValue string //锁的value client *redis.Client } InvokeMethod func(args ...interface{}) (interface{}, error) ) func NewRedisLock(client *redis.Client) *RedisLock { return &RedisLock{ lockChan: make(chan struct{}, 1), lockKey: uuid.NewString(), client: client, } } func (this *RedisLock) TryLock(method InvokeMethod, timeout time.Duration) (interface{}, error) { this.rwLock.Lock() defer this.rwLock.Unlock() var err error go func() { for { //如果这个lockValue此时有值,说明上一个人在使用中,那么就一直等待,等待的间隔是5* time.Millisecond if this.lockValue == "" { //此时没有值,说明redis已经将这个值释放了,这时创建一个锁的value,放到reids里 this.lockValue = uuid.New().String() //这里利用redis的SetNX方法,为lock设置一个超时时间,一定时间后,锁会自己清掉数据 //这样做的目的是,比如进程挂了,没有执行UnLock,但利用redis可以把锁给清掉,防止死锁 hasSet, setErr := this.client.SetNX(this.lockKey, this.lockValue, timeout).Result() //锁发生错误,也要退出 if setErr != nil { err = setErr this.lockChan <- struct{}{} return } //当值设置成功后,通知主协程,执行相应方法method,同时整个goroutine退出,return if hasSet { this.lockChan <- struct{}{} return } } //这里是单个锁执行最小的时间间隔,间隔5毫秒相当于每秒最多处理200个任务 //如果太快了,整个goroutine轮巡次数太快,可能上一个任务还没处理完 //可以自己看情况设定,其实这个时间间隔差不多刚刚好 time.Sleep(5 * time.Millisecond) } }() select { case <-this.lockChan: if err != nil { return nil, err } return method() case <-time.After(timeout): return nil, errors.New("lock timeout") } } func (r *RedisLock) UnLock() (bool, error) { if r.lockValue == "" { return false, errors.New("锁已经被释放") } //执行语句释放redis,Lua语句内容:redis里的lock对应的key的value删除 script := "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" //EVAL是执行上面的script的Lua语句 result, err := r.client.Do("EVAL", script, 1, r.lockKey, r.lockValue).Bool() if err != nil { return false, err } if !result { return false, errors.New("出现分布式并发释放锁错误") } r.lockValue = "" return true, nil }