利用redis实现golang的分布式锁
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
}