123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- package redis
- import (
- "math/rand"
- "strconv"
- "sync/atomic"
- "time"
- red "github.com/go-redis/redis"
- "github.com/tal-tech/go-zero/core/logx"
- )
- const (
- letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
- lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
- redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
- return "OK"
- else
- return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
- end`
- delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
- return redis.call("DEL", KEYS[1])
- else
- return 0
- end`
- randomLen = 16
- tolerance = 500 // milliseconds
- millisPerSecond = 1000
- )
- // A RedisLock is a redis lock.
- type RedisLock struct {
- store *Redis
- seconds uint32
- key string
- id string
- }
- func init() {
- rand.Seed(time.Now().UnixNano())
- }
- // NewRedisLock returns a RedisLock.
- func NewRedisLock(store *Redis, key string) *RedisLock {
- return &RedisLock{
- store: store,
- key: key,
- id: randomStr(randomLen),
- }
- }
- // Acquire acquires the lock.
- func (rl *RedisLock) Acquire() (bool, error) {
- seconds := atomic.LoadUint32(&rl.seconds)
- resp, err := rl.store.Eval(lockCommand, []string{rl.key}, []string{
- rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance)})
- if err == red.Nil {
- return false, nil
- } else if err != nil {
- logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
- return false, err
- } else if resp == nil {
- return false, nil
- }
- reply, ok := resp.(string)
- if ok && reply == "OK" {
- return true, nil
- }
- logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
- return false, nil
- }
- // Release releases the lock.
- func (rl *RedisLock) Release() (bool, error) {
- resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id})
- if err != nil {
- return false, err
- }
- reply, ok := resp.(int64)
- if !ok {
- return false, nil
- }
- return reply == 1, nil
- }
- // SetExpire sets the expire.
- func (rl *RedisLock) SetExpire(seconds int) {
- atomic.StoreUint32(&rl.seconds, uint32(seconds))
- }
- func randomStr(n int) string {
- b := make([]byte, n)
- for i := range b {
- b[i] = letters[rand.Intn(len(letters))]
- }
- return string(b)
- }
|