github.com/godaddy-x/freego@v1.0.156/cache/redis_lock.go (about)

     1  package cache
     2  
     3  import (
     4  	"github.com/garyburd/redigo/redis"
     5  	"github.com/godaddy-x/freego/ex"
     6  	"github.com/godaddy-x/freego/utils"
     7  	"github.com/godaddy-x/freego/zlog"
     8  	"time"
     9  )
    10  
    11  const (
    12  	lockSuccess  = "OK"
    13  	lockKey      = "redis:lock:"
    14  	subscribeKey = "redis:subscribe:lock:"
    15  )
    16  
    17  var unlockScript = redis.NewScript(2, `
    18  	if redis.call("get", KEYS[1]) == ARGV[1]
    19  	then
    20  		local res = redis.call("del", KEYS[1])
    21  		if ARGV[3] == "redis:subscribe:lock:"
    22  		then
    23  			redis.call("PUBLISH", KEYS[2], ARGV[2])
    24  		end
    25  		return res
    26  	else
    27  		return 0
    28  	end
    29  `)
    30  
    31  // Lock represents a held lock.
    32  type Lock struct {
    33  	resource string
    34  	token    string
    35  	conn     redis.Conn
    36  	exp      time.Duration // second
    37  	spin     bool
    38  }
    39  
    40  func (lock *Lock) key() string {
    41  	return utils.AddStr(lockKey, lock.resource)
    42  }
    43  
    44  func (lock *Lock) subscribeKey() string {
    45  	return subscribeKey
    46  }
    47  
    48  func (lock *Lock) subscribeData() string {
    49  	return utils.AddStr(subscribeKey, lock.resource)
    50  }
    51  
    52  func (lock *Lock) tryLock() (ok bool, err error) {
    53  	status, err := redis.String(lock.conn.Do("SET", lock.key(), lock.token, "EX", int64(lock.exp/time.Second), "NX"))
    54  	if err == redis.ErrNil {
    55  		// The lock was not successful, it already exists.
    56  		return false, nil
    57  	}
    58  	if err != nil {
    59  		return false, err
    60  	}
    61  	return status == lockSuccess, nil
    62  }
    63  
    64  func (lock *Lock) unlock() {
    65  	var argv3 string
    66  	if lock.spin {
    67  		argv3 = subscribeKey
    68  	}
    69  	_, err := unlockScript.Do(lock.conn, lock.key(), lock.subscribeKey(), lock.token, lock.subscribeData(), argv3)
    70  	if err != nil {
    71  		zlog.Error("unlockScript failed:", 0, zlog.AddError(err))
    72  	}
    73  	if lock != nil && lock.conn != nil {
    74  		if err := lock.conn.Close(); err != nil {
    75  			zlog.Error("lock conn close failed", 0, zlog.AddError(err))
    76  		}
    77  	}
    78  }
    79  
    80  func (self *RedisManager) getLockWithTimeout(conn redis.Conn, resource string, expSecond time.Duration, spin bool) (lock *Lock, ok bool, err error) {
    81  	lock = &Lock{
    82  		resource: resource,
    83  		token:    utils.NextSID(),
    84  		conn:     conn,
    85  		exp:      expSecond,
    86  		spin:     spin,
    87  	}
    88  	ok, err = lock.tryLock()
    89  	if !ok || err != nil {
    90  		if lock.spin {
    91  			return
    92  		}
    93  		self.Close(conn)
    94  		lock = nil
    95  	}
    96  	return
    97  }
    98  
    99  func (self *RedisManager) checkParameters(resource string, trySecond, expSecond int, call func() error) error {
   100  	if len(resource) == 0 || len(resource) > 100 {
   101  		return utils.Error("redis lock key invalid: ", resource)
   102  	}
   103  	if expSecond < 3 || expSecond > 600 {
   104  		return utils.Error("redis lock exp range [3-600s]: ", expSecond)
   105  	}
   106  	if trySecond < 3 || trySecond > 600 || trySecond > expSecond {
   107  		return utils.Error("redis lock try range [3-600s]: ", trySecond)
   108  	}
   109  	if call == nil {
   110  		return utils.Error("redis lock call function nil")
   111  	}
   112  	return nil
   113  }
   114  
   115  func (self *RedisManager) SpinLockWithTimeout(resource string, trySecond, expSecond int, call func() error) error {
   116  	if err := self.checkParameters(resource, trySecond, expSecond, call); err != nil {
   117  		return err
   118  	}
   119  	client := self.Pool.Get()
   120  	lock, ok, err := self.getLockWithTimeout(client, resource, time.Duration(expSecond)*time.Second, true)
   121  	if err != nil || !ok {
   122  		if err := self.Subscribe(lock.subscribeKey(), trySecond, func(msg string) (bool, error) {
   123  			if msg != lock.subscribeData() {
   124  				return false, nil
   125  			}
   126  			lock, ok, err = self.getLockWithTimeout(client, resource, time.Duration(expSecond)*time.Second, true)
   127  			if err != nil || !ok {
   128  				return false, err
   129  			}
   130  			return true, nil
   131  		}); err != nil {
   132  			self.Close(lock.conn)
   133  			lock = nil
   134  			return ex.Throw{Code: ex.REDIS_LOCK_TIMEOUT, Msg: "spin locker acquire timeout", Err: err}
   135  		}
   136  	}
   137  	err = call()
   138  	lock.unlock()
   139  	return err
   140  }
   141  
   142  func (self *RedisManager) TryLockWithTimeout(resource string, expSecond int, call func() error) error {
   143  	if err := self.checkParameters(resource, expSecond, expSecond, call); err != nil {
   144  		return err
   145  	}
   146  	client := self.Pool.Get()
   147  	lock, ok, err := self.getLockWithTimeout(client, resource, time.Duration(expSecond)*time.Second, false)
   148  	if err != nil {
   149  		return ex.Throw{Code: ex.REDIS_LOCK_ACQUIRE, Msg: "locker acquire failed", Err: err}
   150  	}
   151  	if !ok {
   152  		return ex.Throw{Code: ex.REDIS_LOCK_PENDING, Msg: "locker pending"}
   153  	}
   154  	err = call()
   155  	lock.unlock()
   156  	return err
   157  }
   158  
   159  func SpinLocker(lockObj, errorMsg string, trySecond, expSecond int, callObj func() error) error {
   160  	rds, err := NewRedis()
   161  	if err != nil {
   162  		return ex.Throw{Code: ex.CACHE, Msg: ex.CACHE_ERR, Err: err}
   163  	}
   164  	if err := rds.SpinLockWithTimeout(lockObj, trySecond, expSecond, callObj); err != nil {
   165  		if len(errorMsg) > 0 {
   166  			r := ex.Catch(err)
   167  			if r.Code == ex.REDIS_LOCK_ACQUIRE {
   168  				return err
   169  			} else if r.Code == ex.REDIS_LOCK_PENDING {
   170  				return ex.Throw{Code: r.Code, Msg: errorMsg}
   171  			} else if r.Code == ex.REDIS_LOCK_TIMEOUT {
   172  				return err
   173  			}
   174  		}
   175  		return err
   176  	}
   177  	return nil
   178  }
   179  
   180  func TryLocker(lockObj, errorMsg string, expSecond int, callObj func() error) error {
   181  	rds, err := NewRedis()
   182  	if err != nil {
   183  		return ex.Throw{Code: ex.CACHE, Msg: ex.CACHE_ERR, Err: err}
   184  	}
   185  	if err := rds.TryLockWithTimeout(lockObj, expSecond, callObj); err != nil {
   186  		if len(errorMsg) > 0 {
   187  			r := ex.Catch(err)
   188  			if r.Code == ex.REDIS_LOCK_ACQUIRE {
   189  				return err
   190  			} else if r.Code == ex.REDIS_LOCK_PENDING {
   191  				return ex.Throw{Code: r.Code, Msg: errorMsg}
   192  			}
   193  		}
   194  		return err
   195  	}
   196  	return nil
   197  }