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 }