package redis_client
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/google/uuid"
"time"
)
type RedisLock struct {
redisClient *redis.Client
wait bool // 是否等待
lockKey string
lockValue string
timeout time.Duration
waitTimeout time.Duration
retryInterval time.Duration
}
func NewRedisLock(redisClient *redis.Client, lockKey string) *RedisLock {
return &RedisLock{
redisClient: redisClient,
wait: true,
lockKey: fmt.Sprintf("s:lock:%s", lockKey),
timeout: time.Second * 5,
waitTimeout: time.Second * 5,
retryInterval: time.Millisecond * 100,
}
}
func (rl *RedisLock) SetWait(wait bool) *RedisLock {
rl.wait = wait
return rl
}
func (rl *RedisLock) SetTimeout(timeout time.Duration) *RedisLock {
rl.timeout = timeout
return rl
}
func (rl *RedisLock) SetWaitTimeout(waitTimeout time.Duration) *RedisLock {
rl.waitTimeout = waitTimeout
return rl
}
func (rl *RedisLock) SetRetryInterval(retryInterval time.Duration) *RedisLock {
rl.retryInterval = retryInterval
return rl
}
func (rl *RedisLock) Lock() (bool, error) {
// 生成随机 UUID 作为锁的值
rl.lockValue = uuid.New().String()
ctx, cancel := context.WithTimeout(context.Background(), rl.waitTimeout)
defer cancel()
// 循环尝试获取锁
for {
select {
case <-ctx.Done():
return false, nil
default:
success, err := rl.redisClient.SetNX(ctx, rl.lockKey, rl.lockValue, rl.timeout).Result()
if err != nil || (!success && !rl.wait) {
return false, err
}
if success {
return true, nil
}
time.Sleep(rl.retryInterval)
}
}
}
func (rl *RedisLock) Unlock() (bool, error) {
script := "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := rl.redisClient.Eval(ctx, script, []string{rl.lockKey}, rl.lockValue).Int()
if err != nil {
return false, err
}
return result > 0, nil
}
网友评论