├── README.md ├── VERSION.md ├── cache.go ├── cache_test.go ├── example └── main.go ├── internal ├── hystrix │ ├── counter.go │ └── hystrix.go ├── redigo │ ├── LICENSE │ ├── internal │ │ └── commandinfo.go │ └── redis │ │ ├── conn.go │ │ ├── doc.go │ │ ├── go17.go │ │ ├── log.go │ │ ├── pool.go │ │ ├── pre_go17.go │ │ ├── pubsub.go │ │ ├── redis.go │ │ ├── reply.go │ │ ├── scan.go │ │ └── script.go ├── redisutil.go └── redisutil_test.go ├── redis ├── cache_redis.go └── cache_redis_test.go └── runtime ├── cache_runtime.go └── cache_runtime_test.go /README.md: -------------------------------------------------------------------------------- 1 | # devfeel/cache 2 | Simple and easy go cache framework, support runtime & redis -------------------------------------------------------------------------------- /VERSION.md: -------------------------------------------------------------------------------- 1 | ## cache版本记录: 2 | 3 | #### Version 0.7.2 4 | * New Feature: Add Command - RedisCache.ZREVRangeByScore(key string, max, min string, isWithScores bool)([]string, error) 5 | - 2018-10-10 15:00 6 | 7 | #### Version 0.7.1 8 | - New Feature: Add hystrix module 9 | - New Feature: RedisCache ReadOnly\Backup add hystrix check 10 | - Detail: 11 | - 1、hystrix only use to read command 12 | - 2、if set ReadOnlyServer, hystrix only check readonly server conn 13 | - 3、if only set DefaultServer and BackupServer, hystrix only check default server conn 14 | - 2018-08-17 15:00 15 | 16 | #### Version 0.7 17 | - New Feature: RedisCache add BackupServer\ReadOnlyServer 18 | - you can use SetReadOnlyServer or SetBackupServer to set redis info 19 | - Detail: 20 | - 1、if set ReadOnlyServer, all read command will use this server config 21 | - 2、BackupServer only can use to read command 22 | - 3、if set BackupServer, if read command conn server failed, will auto use this config 23 | - Example: 24 | ``` golang 25 | redisServer := "redis://192.168.8.175:6329/0" 26 | readOnlyServer := "redis://192.168.8.175:6339/0" 27 | backupRedisServer := "redis://192.168.8.175:6379/0" 28 | redisCache := cache.GetRedisCachePoolConf(redisServer, 10, 100) 29 | redisCache.SetReadOnlyServer(readOnlyServer, 10, 100) 30 | redisCache.SetBackupServer(backupRedisServer, 10, 100) 31 | ``` 32 | - 2018-08-16 11:00 33 | 34 | #### Version 0.6.6 35 | * New Command: RedisCache.Publish(channel string, message interface{})(int64, error) 36 | * New Command: RedisCache.Subscribe(receive chan redis.Message, channels ...interface{})error 37 | * Example: 38 | ``` golang 39 | func TestRedisCache_Subscribe(t *testing.T) { 40 | receive := make(chan Message, 1000) 41 | err := rc.Subscribe(receive, "channel-test") 42 | if err != nil{ 43 | t.Error("TestRedisCache_Subscribe error", err) 44 | } 45 | for{ 46 | select{ 47 | case msg := <- receive: 48 | fmt.Println(msg.Channel, msg.Data) 49 | } 50 | } 51 | } 52 | ``` 53 | * 2018-08-03 11:00 54 | 55 | #### Version 0.6.5 56 | * Update Command: ZRangeByScore(key string, start, stop string, isWithScores bool)([]string, error) 57 | * 2018-07-26 18:00 58 | 59 | #### Version 0.6.4 60 | * New Command: ZRank(key, member string) (int, error) 61 | * New Command: ZRangeByScore(key string, start, stop string)([]string, error) 62 | * 2018-07-25 14:00 63 | 64 | #### Version 0.6.3 65 | * New Command: ZRem(key string, member... interface{})(int, error) 66 | * 2018-07-17 13:00 67 | 68 | #### Version 0.6.2 69 | * New feature: Cache add Expire, used to set expire time on key 70 | * Support RuntimeCache & RedisCache 71 | * 2018-06-23 21:00 72 | 73 | #### Version 0.6.1 74 | * Bug Fixed:cache_redis issue #5 当ttl设置为0时,redis会返回“ERR invalid expire time in set”,不是forever 75 | * 2018-05-30 09:00 76 | 77 | #### Version 0.6 78 | * 新增GetRedisCachePoolConf接口,用于需要设置连接池配置的场景 79 | * 默认GetRedisCache与GetCache使用redis时,连接池设置默认使用RedisConnPool_MaxIdle, RedisConnPool_MaxActive 80 | * 2018-05-09 18:00 81 | 82 | #### Version 0.5 83 | * RedisCache新增ZRange、ZRevRange、ZCard、HMGet接口 84 | * 2018-04-25 20:00 85 | 86 | #### Version 0.4 87 | * RedisCache新增EVAL接口 88 | * 2018-04-24 16:00 89 | 90 | 91 | #### Version 0.3 92 | * RedisCache新增ZAdd、ZCount接口 93 | * 2018-04-04 17:00 94 | 95 | #### Version 0.2 96 | * RedisCache新增GetJsonObj、SetJsonObj接口 97 | * 2018-03-14 09:00 98 | 99 | #### Version 0.1 100 | * 初始版本,支持runtime & redis 101 | * 2018-03-13 18:00 -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/devfeel/cache/redis" 5 | "github.com/devfeel/cache/runtime" 6 | "sync" 7 | ) 8 | 9 | const ( 10 | CacheType_Runtime = "runtime" 11 | CacheType_Redis = "redis" 12 | ) 13 | 14 | const ( 15 | RedisConnPool_MaxIdle = 5 16 | RedisConnPool_MaxActive = 20 17 | ) 18 | 19 | var ( 20 | runtime_cache Cache 21 | redisCacheMap map[string]RedisCache 22 | redisCacheLock *sync.RWMutex 23 | ) 24 | 25 | func init() { 26 | redisCacheMap = make(map[string]RedisCache) 27 | redisCacheLock = new(sync.RWMutex) 28 | } 29 | 30 | type ( 31 | Cache interface { 32 | // Exist return true if value cached by given key 33 | Exists(key string) (bool, error) 34 | // Get returns value by given key 35 | Get(key string) (interface{}, error) 36 | // GetString returns value string format by given key 37 | GetString(key string) (string, error) 38 | // GetInt returns value int format by given key 39 | GetInt(key string) (int, error) 40 | // GetInt64 returns value int64 format by given key 41 | GetInt64(key string) (int64, error) 42 | // Set cache value by given key 43 | Set(key string, v interface{}, ttl int64) error 44 | // Incr increases int64-type value by given key as a counter 45 | // if key not exist, before increase set value with zero 46 | Incr(key string) (int64, error) 47 | // Decr decreases int64-type value by given key as a counter 48 | // if key not exist, before increase set value with zero 49 | Decr(key string) (int64, error) 50 | // Delete delete cache item by given key 51 | Delete(key string) error 52 | // ClearAll clear all cache items 53 | ClearAll() error 54 | // Expire Set a timeout on key. After the timeout has expired, the key will automatically be deleted. 55 | // timeout time duration is second 56 | Expire(key string, timeOutSeconds int) (int, error) 57 | } 58 | 59 | RedisCache interface { 60 | Cache 61 | // SetReadOnlyServer set readonly redis server 62 | SetReadOnlyServer(serverUrl string, maxIdle int, maxActive int) 63 | // SetBackupServer set backup redis server, only use to read 64 | SetBackupServer(serverUrl string, maxIdle int, maxActive int) 65 | 66 | /*---------- Hash -----------*/ 67 | // HGet Returns the value associated with field in the hash stored at key. 68 | HGet(hashID string, field string) (string, error) 69 | // HMGet Returns the values associated with the specified fields in the hash stored at key. 70 | HMGet(hashID string, field ...interface{}) ([]string, error) 71 | // HSet Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created. 72 | // If field already exists in the hash, it is overwritten. 73 | HSet(hashID string, field string, val string) error 74 | // HGetAll Returns all fields and values of the hash stored at key 75 | HGetAll(hashID string) (map[string]string, error) 76 | // HSetNX Sets field in the hash stored at key to value, only if field does not yet exist 77 | HSetNX(hashID string, field string, val string) (string, error) 78 | // HDel Removes the specified fields from the hash stored at key. 79 | HDel(hashID string, fields ...interface{}) (int, error) 80 | // HExists Returns if field is an existing field in the hash stored at key 81 | HExists(hashID string, field string) (int, error) 82 | // HIncrBy Increments the number stored at field in the hash stored at key by increment. 83 | HIncrBy(hashID string, field string, increment int) (int, error) 84 | // HIncrByFloat Increment the specified field of a hash stored at key, and representing a floating point number, by the specified increment 85 | HIncrByFloat(hashID string, field string, increment float64) (float64, error) 86 | // HKeys Returns all field names in the hash stored at key. 87 | HKeys(hashID string) ([]string, error) 88 | // HLen Returns the number of fields contained in the hash stored at key 89 | HLen(hashID string) (int, error) 90 | // HVals Returns all values in the hash stored at key 91 | HVals(hashID string) ([]string, error) 92 | // GetJsonObj get obj with SetJsonObj key 93 | GetJsonObj(key string, result interface{}) error 94 | // SetJsonObj set obj use json encode string 95 | SetJsonObj(key string, val interface{}) (interface{}, error) 96 | 97 | /*---------- List -----------*/ 98 | // BLPop BLPOP is a blocking list pop primitive. 99 | // It is the blocking version of LPOP because it blocks the connection when there are no elements to pop from any of the given lists 100 | BLPop(key ...interface{}) (map[string]string, error) 101 | // BRPOP is a blocking list pop primitive 102 | // It is the blocking version of RPOP because it blocks the connection when there are no elements to pop from any of the given lists 103 | BRPop(key ...interface{}) (map[string]string, error) 104 | // BRPOPLPUSH is a operation like RPOPLPUSH but blocking 105 | BRPopLPush(source string, destination string) (string, error) 106 | // LIndex return element which subscript is index, 107 | // if index is -1, return last one element of list and so on 108 | LIndex(key string, index int) (string, error) 109 | // LInsert Inserts value in the list stored at key either before or after the reference value pivot. 110 | LInsert(key string, direction string, pivot string, value string) (int, error) 111 | // LLen return length of list 112 | LLen(key string) (int, error) 113 | // LPop remove and return head element of list 114 | LPop(key string) (string, error) 115 | // LPush Insert all the specified values at the head of the list stored at key 116 | LPush(key string, value ...interface{}) (int, error) 117 | // LPushX insert an element at the head of the list 118 | LPushX(key string, value string) (int, error) 119 | // LRange Returns the specified elements of the list stored at key 120 | LRange(key string, start int, end int) ([]string, error) 121 | // LRem Removes the first count occurrences of elements equal to value from the list stored at key. 122 | LRem(key string, count int, value string) (int, error) 123 | // LSet Sets the list element at index to value 124 | LSet(key string, index int, value string) (string, error) 125 | // LTrim Trim an existing list so that it will contain only the specified range of elements specified 126 | LTrim(key string, start int, stop int) (string, error) 127 | // RPop Removes and returns the last element of the list stored at key 128 | RPop(key string) (string, error) 129 | // RPopLPush Atomically returns and removes the last element (tail) of the list stored at source, and pushes the element at the first element (head) of the list stored at destination 130 | RPopLPush(source string, destination string) (string, error) 131 | // RPush Insert all the specified values at the tail of the list stored at key. 132 | RPush(key string, value ...interface{}) (int, error) 133 | // RPushX Inserts value at the tail of the list stored at key, only if key already exists and holds a list 134 | RPushX(key string, value ...interface{}) (int, error) 135 | 136 | /*---------- Set -----------*/ 137 | // SAdd Add the specified members to the set stored at key 138 | SAdd(key string, value ...interface{}) (int, error) 139 | // SCard Returns the set cardinality (number of elements) of the set stored at key 140 | SCard(key string) (int, error) 141 | // SDiff Returns the members of the set resulting from the difference between the first set and all the successive sets 142 | SDiff(key ...interface{}) ([]string, error) 143 | // SDiffStore This command is equal to SDIFF, but instead of returning the resulting set, it is stored in destination 144 | SDiffStore(destination string, key ...interface{}) (int, error) 145 | // SInter Returns the members of the set resulting from the intersection of all the given sets. 146 | SInter(key ...interface{}) ([]string, error) 147 | // SInterStore This command is equal to SINTER, but instead of returning the resulting set, it is stored in destination 148 | SInterStore(destination string, key ...interface{}) (int, error) 149 | // SIsMember Returns if member is a member of the set stored at key. 150 | SIsMember(key string, value string) (bool, error) 151 | // SMembers Returns all the members of the set value stored at key. 152 | SMembers(key string) ([]string, error) 153 | // SMove Move member from the set at source to the set at destination 154 | SMove(source string, destination string, value string) (bool, error) 155 | // SPop Removes and returns one or more random elements from the set value store at key. 156 | SPop(key string) (string, error) 157 | // SRandMember When called with just the key argument, return a random element from the set value stored at key 158 | SRandMember(key string, count int) ([]string, error) 159 | // SRem Remove the specified members from the set stored at key 160 | SRem(key string, value ...interface{}) (int, error) 161 | // SUnion Returns the members of the set resulting from the union of all the given sets 162 | SUnion(key ...interface{}) ([]string, error) 163 | // SUnionStore This command is equal to SUNION, but instead of returning the resulting set, it is stored in destination 164 | SUnionStore(destination string, key ...interface{}) (int, error) 165 | 166 | /*---------- sorted set -----------*/ 167 | // ZAdd Adds all the specified members with the specified scores to the sorted set stored at key 168 | ZAdd(key string, score int64, member interface{}) (int, error) 169 | // ZCount Returns the number of elements in the sorted set at key with a score between min and max 170 | ZCount(key string, min, max int64) (int, error) 171 | // ZRem Removes the specified members from the sorted set stored at key. Non existing members are ignored. 172 | ZRem(key string, member ...interface{}) (int, error) 173 | // ZCard Returns the sorted set cardinality (number of elements) of the sorted set stored at key. 174 | ZCard(key string) (int, error) 175 | // ZRank Returns the rank of member in the sorted set stored at key, with the scores ordered from low to high 176 | ZRank(key, member string) (int, error) 177 | // ZRange Returns the specified range of elements in the sorted set stored at key 178 | ZRange(key string, start, stop int64) ([]string, error) 179 | // ZRangeByScore Returns all the elements in the sorted set at key with a score between min and max (including elements with score equal to min or max). 180 | ZRangeByScore(key string, start, stop string, isWithScores bool) ([]string, error) 181 | // ZREVRangeByScore Returns all the elements in the sorted set at key with a score between max and min (including elements with score equal to max or min). In contrary to the default ordering of sorted sets, for this command the elements are considered to be ordered from high to low scores. 182 | ZREVRangeByScore(key string, max, min string, isWithScores bool) ([]string, error) 183 | // ZRange Returns the specified range of elements in the sorted set stored at key 184 | ZRevRange(key string, start, stop int64) ([]string, error) 185 | 186 | //****************** PUB/SUB ********************* 187 | // Publish Posts a message to the given channel. 188 | Publish(channel string, message interface{}) (int64, error) 189 | 190 | // Subscribe Subscribes the client to the specified channels 191 | Subscribe(receive chan redis.Message, channels ...interface{}) error 192 | 193 | //****************** lua scripts ********************* 194 | // EVAL used to evaluate scripts using the Lua interpreter built into Redis starting from version 2.6.0 195 | EVAL(script string, argsNum int, arg ...interface{}) (interface{}, error) 196 | } 197 | ) 198 | 199 | func Must(i interface{}, err error) interface{} { 200 | if err != nil { 201 | panic(err) 202 | } 203 | return i 204 | } 205 | 206 | //get cache by gived ctype 207 | //if set CacheType_Redis, must set serverUrl 208 | func GetCache(ctype string, serverUrl ...string) Cache { 209 | switch ctype { 210 | case CacheType_Runtime: 211 | return GetRuntimeCache() 212 | case CacheType_Redis: 213 | if len(serverUrl) <= 0 { 214 | panic("GetRedisCache lost serverip!") 215 | } 216 | return GetRedisCache(serverUrl[0]) 217 | default: 218 | return GetRuntimeCache() 219 | } 220 | } 221 | 222 | //get runtime cache 223 | func GetRuntimeCache() Cache { 224 | if runtime_cache == nil { 225 | runtime_cache = NewRuntimeCache() 226 | } 227 | return runtime_cache 228 | } 229 | 230 | //get redis cache 231 | //must set serverIp like "redis://:password@10.0.1.11:6379/0" 232 | func GetRedisCache(serverUrl string) RedisCache { 233 | return GetRedisCachePoolConf(serverUrl, RedisConnPool_MaxIdle, RedisConnPool_MaxActive) 234 | } 235 | 236 | //get redis cache 237 | //must set serverIp like "redis://:password@10.0.1.11:6379/0" 238 | func GetRedisCachePoolConf(serverUrl string, maxIdle int, maxActive int) RedisCache { 239 | if maxIdle <= 0 { 240 | maxIdle = RedisConnPool_MaxIdle 241 | } 242 | if maxActive < 0 { 243 | maxActive = RedisConnPool_MaxActive 244 | } 245 | c, ok := redisCacheMap[serverUrl] 246 | if !ok { 247 | c = NewRedisCache(serverUrl, maxIdle, maxActive) 248 | redisCacheLock.Lock() 249 | redisCacheMap[serverUrl] = c 250 | redisCacheLock.Unlock() 251 | return c 252 | 253 | } else { 254 | return c 255 | } 256 | } 257 | 258 | //new runtime cache 259 | func NewRuntimeCache() Cache { 260 | return runtime.NewRuntimeCache() 261 | } 262 | 263 | //new redis cache 264 | //must set serverIp like "redis://:password@10.0.1.11:6379/0" 265 | func NewRedisCache(serverUrl string, maxIdle int, maxActive int) RedisCache { 266 | return redis.NewRedisCache(serverUrl, maxIdle, maxActive) 267 | } 268 | -------------------------------------------------------------------------------- /cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/devfeel/cache" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | c := cache.GetRuntimeCache() 11 | c.Set("1", 1, 100) 12 | fmt.Println(cache.Must(c.GetString("1"))) 13 | 14 | //创建一个新的内存缓存,与之前GetRuntimeCache的不相关 15 | c2 := cache.NewRuntimeCache() 16 | fmt.Println(c2.GetString("1")) 17 | 18 | redisServer := "redis://192.168.8.175:6329/0" 19 | readOnlyServer := "redis://192.168.8.175:6339/0" 20 | backupRedisServer := "redis://192.168.8.175:6379/0" 21 | redisCache := cache.GetRedisCache(redisServer) 22 | redisCache.SetBackupServer(backupRedisServer, 10, 10) 23 | 24 | err := redisCache.HSet("hsettest", "keytest", "1") 25 | if err != nil { 26 | fmt.Println(`redisCache.HSet("hsettest", "keytest", "1")`, err) 27 | } 28 | 29 | _, err = redisCache.Expire("hsettest", 60000) 30 | if err != nil{ 31 | fmt.Println(`redisCache.Expire("hsettest", 60)`, err) 32 | } 33 | fmt.Println(redisCache.HGet("hsettest", "keytest")) 34 | cr := cache.GetRedisCachePoolConf(redisServer, 10, 100) 35 | cr.SetReadOnlyServer(readOnlyServer, 10, 10) 36 | cr.SetBackupServer(backupRedisServer, 10, 10) 37 | err = cr.Set("1", 1, 100000) 38 | if err != nil{ 39 | fmt.Println(`Set("1", 1, 100)`, err) 40 | } 41 | for i:=0;i< 20;i++ { 42 | fmt.Println(cr.GetString("1")) 43 | } 44 | 45 | 46 | 47 | time.Sleep(time.Hour) 48 | } 49 | -------------------------------------------------------------------------------- /internal/hystrix/counter.go: -------------------------------------------------------------------------------- 1 | package hystrix 2 | 3 | import ( 4 | "sync/atomic" 5 | ) 6 | 7 | const ( 8 | minuteTimeLayout = "200601021504" 9 | ) 10 | 11 | // Counter incremented and decremented base on int64 value. 12 | type Counter interface { 13 | Clear() 14 | Count() int64 15 | Dec(int64) 16 | Inc(int64) 17 | } 18 | 19 | // NewCounter constructs a new StandardCounter. 20 | func NewCounter() Counter { 21 | return &StandardCounter{} 22 | } 23 | 24 | 25 | // StandardCounter is the standard implementation of a Counter 26 | type StandardCounter struct { 27 | count int64 28 | } 29 | 30 | // Clear sets the counter to zero. 31 | func (c *StandardCounter) Clear() { 32 | atomic.StoreInt64(&c.count, 0) 33 | } 34 | 35 | // Count returns the current count. 36 | func (c *StandardCounter) Count() int64 { 37 | return atomic.LoadInt64(&c.count) 38 | } 39 | 40 | // Dec decrements the counter by the given amount. 41 | func (c *StandardCounter) Dec(i int64) { 42 | atomic.AddInt64(&c.count, -i) 43 | } 44 | 45 | // Inc increments the counter by the given amount. 46 | func (c *StandardCounter) Inc(i int64) { 47 | atomic.AddInt64(&c.count, i) 48 | } 49 | 50 | -------------------------------------------------------------------------------- /internal/hystrix/hystrix.go: -------------------------------------------------------------------------------- 1 | package hystrix 2 | 3 | import ( 4 | "time" 5 | "sync" 6 | ) 7 | 8 | const( 9 | status_Hystrix = 1 10 | status_Alive = 2 11 | DefaultCheckHystrixInterval = 10 //unit is Second 12 | DefaultCheckAliveInterval = 60 //unit is Second 13 | DefaultCleanHistoryInterval = 60 * 5 //unit is Second 14 | DefaultMaxFailedNumber = 100 15 | DefaultReserveMinutes = 30 16 | ) 17 | 18 | type Hystrix interface{ 19 | // Do begin do check 20 | Do() 21 | // RegisterAliveCheck register check Alive func 22 | RegisterAliveCheck(CheckFunc) 23 | // RegisterHystrixCheck register check Hystrix func 24 | RegisterHystrixCheck(CheckFunc) 25 | // IsHystrix return is Hystrix status 26 | IsHystrix() bool 27 | // TriggerHystrix trigger Hystrix status 28 | TriggerHystrix() 29 | // TriggerAlive trigger Alive status 30 | TriggerAlive() 31 | // SetCheckInterval set interval for doCheckHystric and doCheckAlive, unit is Second 32 | SetCheckInterval(int, int) 33 | 34 | // GetCounter get lasted Counter with time key 35 | GetCounter() Counter 36 | 37 | // SetMaxFailed set max failed count for hystrix default counter 38 | SetMaxFailedNumber(int64) 39 | } 40 | 41 | type CheckFunc func()bool 42 | 43 | type StandHystrix struct{ 44 | status int 45 | checkHystrixFunc CheckFunc 46 | checkHystrixInterval int 47 | checkAliveFunc CheckFunc 48 | checkAliveInterval int 49 | 50 | maxFailedNumber int64 51 | counters *sync.Map 52 | } 53 | 54 | 55 | // NewHystrix create new Hystrix, config with CheckAliveFunc and checkAliveInterval, unit is Minute 56 | func NewHystrix(checkAlive CheckFunc, checkHysrix CheckFunc) Hystrix{ 57 | h := &StandHystrix{ 58 | counters : new(sync.Map), 59 | status:status_Alive, 60 | checkAliveFunc: checkAlive, 61 | checkHystrixFunc:checkHysrix, 62 | checkAliveInterval:DefaultCheckAliveInterval, 63 | checkHystrixInterval:DefaultCheckHystrixInterval, 64 | maxFailedNumber:DefaultMaxFailedNumber, 65 | } 66 | if h.checkHystrixFunc == nil{ 67 | h.checkHystrixFunc = h.defaultCheckHystrix 68 | } 69 | return h 70 | } 71 | 72 | func (h *StandHystrix) Do(){ 73 | go h.doCheck() 74 | go h.doCleanHistoryCounter() 75 | } 76 | 77 | func (h *StandHystrix) SetCheckInterval(hystrixInterval, aliveInterval int){ 78 | h.checkAliveInterval = aliveInterval 79 | h.checkHystrixInterval = hystrixInterval 80 | } 81 | 82 | // SetMaxFailed set max failed count for hystrix default counter 83 | func (h *StandHystrix) SetMaxFailedNumber(number int64){ 84 | h.maxFailedNumber = number 85 | } 86 | 87 | // GetCounter get lasted Counter with time key 88 | func (h *StandHystrix) GetCounter() Counter{ 89 | key := getLastedTimeKey() 90 | var counter Counter 91 | loadCounter, exists := h.counters.Load(key) 92 | if !exists{ 93 | counter = NewCounter() 94 | h.counters.Store(key, counter) 95 | }else{ 96 | counter = loadCounter.(Counter) 97 | } 98 | return counter 99 | } 100 | 101 | 102 | func (h *StandHystrix) IsHystrix() bool{ 103 | return h.status == status_Hystrix 104 | } 105 | 106 | func (h *StandHystrix) RegisterAliveCheck(check CheckFunc){ 107 | h.checkAliveFunc = check 108 | } 109 | 110 | func (h *StandHystrix) RegisterHystrixCheck(check CheckFunc){ 111 | h.checkHystrixFunc = check 112 | } 113 | 114 | func (h *StandHystrix) TriggerHystrix(){ 115 | h.status = status_Hystrix 116 | } 117 | 118 | func (h *StandHystrix) TriggerAlive(){ 119 | h.status = status_Alive 120 | } 121 | 122 | 123 | // doCheck do checkAlive when status is Hystrix or checkHytrix when status is Alive 124 | func (h *StandHystrix) doCheck(){ 125 | if h.checkAliveFunc == nil || h.checkHystrixFunc == nil { 126 | return 127 | } 128 | if h.IsHystrix() { 129 | isAlive := h.checkAliveFunc() 130 | if isAlive { 131 | h.TriggerAlive() 132 | h.GetCounter().Clear() 133 | time.AfterFunc(time.Duration(h.checkHystrixInterval)*time.Second, h.doCheck) 134 | } else { 135 | time.AfterFunc(time.Duration(h.checkAliveInterval)*time.Second, h.doCheck) 136 | } 137 | }else{ 138 | isHystrix := h.checkHystrixFunc() 139 | if isHystrix{ 140 | h.TriggerHystrix() 141 | time.AfterFunc(time.Duration(h.checkAliveInterval)*time.Second, h.doCheck) 142 | }else{ 143 | time.AfterFunc(time.Duration(h.checkHystrixInterval)*time.Second, h.doCheck) 144 | } 145 | } 146 | } 147 | 148 | func (h *StandHystrix) doCleanHistoryCounter(){ 149 | var needRemoveKey []string 150 | now, _ := time.Parse(minuteTimeLayout, time.Now().Format(minuteTimeLayout)) 151 | h.counters.Range(func(k, v interface{}) bool{ 152 | key := k.(string) 153 | if t, err := time.Parse(minuteTimeLayout, key); err != nil { 154 | needRemoveKey = append(needRemoveKey, key) 155 | } else { 156 | if now.Sub(t) > (DefaultReserveMinutes * time.Minute) { 157 | needRemoveKey = append(needRemoveKey, key) 158 | } 159 | } 160 | return true 161 | }) 162 | for _, k := range needRemoveKey { 163 | //fmt.Println(time.Now(), "hystrix doCleanHistoryCounter remove key",k) 164 | h.counters.Delete(k) 165 | } 166 | time.AfterFunc(time.Duration(DefaultCleanHistoryInterval)*time.Second, h.doCleanHistoryCounter) 167 | } 168 | 169 | func (h *StandHystrix) defaultCheckHystrix() bool{ 170 | count := h.GetCounter().Count() 171 | if count > h.maxFailedNumber{ 172 | //fmt.Println(time.Now(), "hystrix triggered!!",count) 173 | return true 174 | }else{ 175 | //fmt.Println(time.Now(), "hystrix untriggered", count) 176 | return false 177 | } 178 | } 179 | 180 | func getLastedTimeKey() string{ 181 | key := time.Now().Format(minuteTimeLayout) 182 | if time.Now().Minute() / 2 != 0{ 183 | key = time.Now().Add(time.Duration(-1*time.Minute)).Format(minuteTimeLayout) 184 | } 185 | return key 186 | } -------------------------------------------------------------------------------- /internal/redigo/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /internal/redigo/internal/commandinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package internal // import "github.com/garyburd/redigo/internal" 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | const ( 22 | WatchState = 1 << iota 23 | MultiState 24 | SubscribeState 25 | MonitorState 26 | ) 27 | 28 | type CommandInfo struct { 29 | Set, Clear int 30 | } 31 | 32 | var commandInfos = map[string]CommandInfo{ 33 | "WATCH": {Set: WatchState}, 34 | "UNWATCH": {Clear: WatchState}, 35 | "MULTI": {Set: MultiState}, 36 | "EXEC": {Clear: WatchState | MultiState}, 37 | "DISCARD": {Clear: WatchState | MultiState}, 38 | "PSUBSCRIBE": {Set: SubscribeState}, 39 | "SUBSCRIBE": {Set: SubscribeState}, 40 | "MONITOR": {Set: MonitorState}, 41 | } 42 | 43 | func init() { 44 | for n, ci := range commandInfos { 45 | commandInfos[strings.ToLower(n)] = ci 46 | } 47 | } 48 | 49 | func LookupCommandInfo(commandName string) CommandInfo { 50 | if ci, ok := commandInfos[commandName]; ok { 51 | return ci 52 | } 53 | return commandInfos[strings.ToUpper(commandName)] 54 | } 55 | -------------------------------------------------------------------------------- /internal/redigo/redis/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bufio" 19 | "bytes" 20 | "crypto/tls" 21 | "errors" 22 | "fmt" 23 | "io" 24 | "net" 25 | "net/url" 26 | "regexp" 27 | "strconv" 28 | "sync" 29 | "time" 30 | ) 31 | 32 | // conn is the low-level implementation of Conn 33 | type conn struct { 34 | 35 | // Shared 36 | mu sync.Mutex 37 | pending int 38 | err error 39 | conn net.Conn 40 | 41 | // Read 42 | readTimeout time.Duration 43 | br *bufio.Reader 44 | 45 | // Write 46 | writeTimeout time.Duration 47 | bw *bufio.Writer 48 | 49 | // Scratch space for formatting argument length. 50 | // '*' or '$', length, "\r\n" 51 | lenScratch [32]byte 52 | 53 | // Scratch space for formatting integers and floats. 54 | numScratch [40]byte 55 | } 56 | 57 | // DialTimeout acts like Dial but takes timeouts for establishing the 58 | // connection to the server, writing a command and reading a reply. 59 | // 60 | // Deprecated: Use Dial with options instead. 61 | func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { 62 | return Dial(network, address, 63 | DialConnectTimeout(connectTimeout), 64 | DialReadTimeout(readTimeout), 65 | DialWriteTimeout(writeTimeout)) 66 | } 67 | 68 | // DialOption specifies an option for dialing a Redis server. 69 | type DialOption struct { 70 | f func(*dialOptions) 71 | } 72 | 73 | type dialOptions struct { 74 | readTimeout time.Duration 75 | writeTimeout time.Duration 76 | dial func(network, addr string) (net.Conn, error) 77 | db int 78 | password string 79 | dialTLS bool 80 | skipVerify bool 81 | tlsConfig *tls.Config 82 | } 83 | 84 | // DialReadTimeout specifies the timeout for reading a single command reply. 85 | func DialReadTimeout(d time.Duration) DialOption { 86 | return DialOption{func(do *dialOptions) { 87 | do.readTimeout = d 88 | }} 89 | } 90 | 91 | // DialWriteTimeout specifies the timeout for writing a single command. 92 | func DialWriteTimeout(d time.Duration) DialOption { 93 | return DialOption{func(do *dialOptions) { 94 | do.writeTimeout = d 95 | }} 96 | } 97 | 98 | // DialConnectTimeout specifies the timeout for connecting to the Redis server. 99 | func DialConnectTimeout(d time.Duration) DialOption { 100 | return DialOption{func(do *dialOptions) { 101 | dialer := net.Dialer{Timeout: d} 102 | do.dial = dialer.Dial 103 | }} 104 | } 105 | 106 | // DialNetDial specifies a custom dial function for creating TCP 107 | // connections. If this option is left out, then net.Dial is 108 | // used. DialNetDial overrides DialConnectTimeout. 109 | func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { 110 | return DialOption{func(do *dialOptions) { 111 | do.dial = dial 112 | }} 113 | } 114 | 115 | // DialDatabase specifies the database to select when dialing a connection. 116 | func DialDatabase(db int) DialOption { 117 | return DialOption{func(do *dialOptions) { 118 | do.db = db 119 | }} 120 | } 121 | 122 | // DialPassword specifies the password to use when connecting to 123 | // the Redis server. 124 | func DialPassword(password string) DialOption { 125 | return DialOption{func(do *dialOptions) { 126 | do.password = password 127 | }} 128 | } 129 | 130 | // DialTLSConfig specifies the config to use when a TLS connection is dialed. 131 | // Has no effect when not dialing a TLS connection. 132 | func DialTLSConfig(c *tls.Config) DialOption { 133 | return DialOption{func(do *dialOptions) { 134 | do.tlsConfig = c 135 | }} 136 | } 137 | 138 | // DialTLSSkipVerify to disable server name verification when connecting 139 | // over TLS. Has no effect when not dialing a TLS connection. 140 | func DialTLSSkipVerify(skip bool) DialOption { 141 | return DialOption{func(do *dialOptions) { 142 | do.skipVerify = skip 143 | }} 144 | } 145 | 146 | // Dial connects to the Redis server at the given network and 147 | // address using the specified options. 148 | func Dial(network, address string, options ...DialOption) (Conn, error) { 149 | do := dialOptions{ 150 | dial: net.Dial, 151 | } 152 | for _, option := range options { 153 | option.f(&do) 154 | } 155 | 156 | netConn, err := do.dial(network, address) 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | if do.dialTLS { 162 | tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify) 163 | if tlsConfig.ServerName == "" { 164 | host, _, err := net.SplitHostPort(address) 165 | if err != nil { 166 | netConn.Close() 167 | return nil, err 168 | } 169 | tlsConfig.ServerName = host 170 | } 171 | 172 | tlsConn := tls.Client(netConn, tlsConfig) 173 | if err := tlsConn.Handshake(); err != nil { 174 | netConn.Close() 175 | return nil, err 176 | } 177 | netConn = tlsConn 178 | } 179 | 180 | c := &conn{ 181 | conn: netConn, 182 | bw: bufio.NewWriter(netConn), 183 | br: bufio.NewReader(netConn), 184 | readTimeout: do.readTimeout, 185 | writeTimeout: do.writeTimeout, 186 | } 187 | 188 | if do.password != "" { 189 | if _, err := c.Do("AUTH", do.password); err != nil { 190 | netConn.Close() 191 | return nil, err 192 | } 193 | } 194 | 195 | if do.db != 0 { 196 | if _, err := c.Do("SELECT", do.db); err != nil { 197 | netConn.Close() 198 | return nil, err 199 | } 200 | } 201 | 202 | return c, nil 203 | } 204 | 205 | func dialTLS(do *dialOptions) { 206 | do.dialTLS = true 207 | } 208 | 209 | var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) 210 | 211 | // DialURL connects to a Redis server at the given URL using the Redis 212 | // URI scheme. URLs should follow the draft IANA specification for the 213 | // scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). 214 | func DialURL(rawurl string, options ...DialOption) (Conn, error) { 215 | u, err := url.Parse(rawurl) 216 | if err != nil { 217 | return nil, err 218 | } 219 | 220 | if u.Scheme != "redis" && u.Scheme != "rediss" { 221 | return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) 222 | } 223 | 224 | // As per the IANA draft spec, the host defaults to localhost and 225 | // the port defaults to 6379. 226 | host, port, err := net.SplitHostPort(u.Host) 227 | if err != nil { 228 | // assume port is missing 229 | host = u.Host 230 | port = "6379" 231 | } 232 | if host == "" { 233 | host = "localhost" 234 | } 235 | address := net.JoinHostPort(host, port) 236 | 237 | if u.User != nil { 238 | password, isSet := u.User.Password() 239 | if isSet { 240 | options = append(options, DialPassword(password)) 241 | } 242 | } 243 | 244 | match := pathDBRegexp.FindStringSubmatch(u.Path) 245 | if len(match) == 2 { 246 | db := 0 247 | if len(match[1]) > 0 { 248 | db, err = strconv.Atoi(match[1]) 249 | if err != nil { 250 | return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) 251 | } 252 | } 253 | if db != 0 { 254 | options = append(options, DialDatabase(db)) 255 | } 256 | } else if u.Path != "" { 257 | return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) 258 | } 259 | 260 | if u.Scheme == "rediss" { 261 | options = append([]DialOption{{dialTLS}}, options...) 262 | } 263 | 264 | return Dial("tcp", address, options...) 265 | } 266 | 267 | // NewConn returns a new Redigo connection for the given net connection. 268 | func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { 269 | return &conn{ 270 | conn: netConn, 271 | bw: bufio.NewWriter(netConn), 272 | br: bufio.NewReader(netConn), 273 | readTimeout: readTimeout, 274 | writeTimeout: writeTimeout, 275 | } 276 | } 277 | 278 | func (c *conn) Close() error { 279 | c.mu.Lock() 280 | err := c.err 281 | if c.err == nil { 282 | c.err = errors.New("redigo: closed") 283 | err = c.conn.Close() 284 | } 285 | c.mu.Unlock() 286 | return err 287 | } 288 | 289 | func (c *conn) fatal(err error) error { 290 | c.mu.Lock() 291 | if c.err == nil { 292 | c.err = err 293 | // Close connection to force errors on subsequent calls and to unblock 294 | // other reader or writer. 295 | c.conn.Close() 296 | } 297 | c.mu.Unlock() 298 | return err 299 | } 300 | 301 | func (c *conn) Err() error { 302 | c.mu.Lock() 303 | err := c.err 304 | c.mu.Unlock() 305 | return err 306 | } 307 | 308 | func (c *conn) writeLen(prefix byte, n int) error { 309 | c.lenScratch[len(c.lenScratch)-1] = '\n' 310 | c.lenScratch[len(c.lenScratch)-2] = '\r' 311 | i := len(c.lenScratch) - 3 312 | for { 313 | c.lenScratch[i] = byte('0' + n%10) 314 | i -= 1 315 | n = n / 10 316 | if n == 0 { 317 | break 318 | } 319 | } 320 | c.lenScratch[i] = prefix 321 | _, err := c.bw.Write(c.lenScratch[i:]) 322 | return err 323 | } 324 | 325 | func (c *conn) writeString(s string) error { 326 | c.writeLen('$', len(s)) 327 | c.bw.WriteString(s) 328 | _, err := c.bw.WriteString("\r\n") 329 | return err 330 | } 331 | 332 | func (c *conn) writeBytes(p []byte) error { 333 | c.writeLen('$', len(p)) 334 | c.bw.Write(p) 335 | _, err := c.bw.WriteString("\r\n") 336 | return err 337 | } 338 | 339 | func (c *conn) writeInt64(n int64) error { 340 | return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) 341 | } 342 | 343 | func (c *conn) writeFloat64(n float64) error { 344 | return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) 345 | } 346 | 347 | func (c *conn) writeCommand(cmd string, args []interface{}) (err error) { 348 | c.writeLen('*', 1+len(args)) 349 | err = c.writeString(cmd) 350 | for _, arg := range args { 351 | if err != nil { 352 | break 353 | } 354 | switch arg := arg.(type) { 355 | case string: 356 | err = c.writeString(arg) 357 | case []byte: 358 | err = c.writeBytes(arg) 359 | case int: 360 | err = c.writeInt64(int64(arg)) 361 | case int64: 362 | err = c.writeInt64(arg) 363 | case float64: 364 | err = c.writeFloat64(arg) 365 | case bool: 366 | if arg { 367 | err = c.writeString("1") 368 | } else { 369 | err = c.writeString("0") 370 | } 371 | case nil: 372 | err = c.writeString("") 373 | default: 374 | var buf bytes.Buffer 375 | fmt.Fprint(&buf, arg) 376 | err = c.writeBytes(buf.Bytes()) 377 | } 378 | } 379 | return err 380 | } 381 | 382 | type protocolError string 383 | 384 | func (pe protocolError) Error() string { 385 | return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) 386 | } 387 | 388 | func (c *conn) readLine() ([]byte, error) { 389 | p, err := c.br.ReadSlice('\n') 390 | if err == bufio.ErrBufferFull { 391 | return nil, protocolError("long response line") 392 | } 393 | if err != nil { 394 | return nil, err 395 | } 396 | i := len(p) - 2 397 | if i < 0 || p[i] != '\r' { 398 | return nil, protocolError("bad response line terminator") 399 | } 400 | return p[:i], nil 401 | } 402 | 403 | // parseLen parses bulk string and array lengths. 404 | func parseLen(p []byte) (int, error) { 405 | if len(p) == 0 { 406 | return -1, protocolError("malformed length") 407 | } 408 | 409 | if p[0] == '-' && len(p) == 2 && p[1] == '1' { 410 | // handle $-1 and $-1 null replies. 411 | return -1, nil 412 | } 413 | 414 | var n int 415 | for _, b := range p { 416 | n *= 10 417 | if b < '0' || b > '9' { 418 | return -1, protocolError("illegal bytes in length") 419 | } 420 | n += int(b - '0') 421 | } 422 | 423 | return n, nil 424 | } 425 | 426 | // parseInt parses an integer reply. 427 | func parseInt(p []byte) (interface{}, error) { 428 | if len(p) == 0 { 429 | return 0, protocolError("malformed integer") 430 | } 431 | 432 | var negate bool 433 | if p[0] == '-' { 434 | negate = true 435 | p = p[1:] 436 | if len(p) == 0 { 437 | return 0, protocolError("malformed integer") 438 | } 439 | } 440 | 441 | var n int64 442 | for _, b := range p { 443 | n *= 10 444 | if b < '0' || b > '9' { 445 | return 0, protocolError("illegal bytes in length") 446 | } 447 | n += int64(b - '0') 448 | } 449 | 450 | if negate { 451 | n = -n 452 | } 453 | return n, nil 454 | } 455 | 456 | var ( 457 | okReply interface{} = "OK" 458 | pongReply interface{} = "PONG" 459 | ) 460 | 461 | func (c *conn) readReply() (interface{}, error) { 462 | line, err := c.readLine() 463 | if err != nil { 464 | return nil, err 465 | } 466 | if len(line) == 0 { 467 | return nil, protocolError("short response line") 468 | } 469 | switch line[0] { 470 | case '+': 471 | switch { 472 | case len(line) == 3 && line[1] == 'O' && line[2] == 'K': 473 | // Avoid allocation for frequent "+OK" response. 474 | return okReply, nil 475 | case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': 476 | // Avoid allocation in PING command benchmarks :) 477 | return pongReply, nil 478 | default: 479 | return string(line[1:]), nil 480 | } 481 | case '-': 482 | return Error(string(line[1:])), nil 483 | case ':': 484 | return parseInt(line[1:]) 485 | case '$': 486 | n, err := parseLen(line[1:]) 487 | if n < 0 || err != nil { 488 | return nil, err 489 | } 490 | p := make([]byte, n) 491 | _, err = io.ReadFull(c.br, p) 492 | if err != nil { 493 | return nil, err 494 | } 495 | if line, err := c.readLine(); err != nil { 496 | return nil, err 497 | } else if len(line) != 0 { 498 | return nil, protocolError("bad bulk string format") 499 | } 500 | return p, nil 501 | case '*': 502 | n, err := parseLen(line[1:]) 503 | if n < 0 || err != nil { 504 | return nil, err 505 | } 506 | r := make([]interface{}, n) 507 | for i := range r { 508 | r[i], err = c.readReply() 509 | if err != nil { 510 | return nil, err 511 | } 512 | } 513 | return r, nil 514 | } 515 | return nil, protocolError("unexpected response line") 516 | } 517 | 518 | func (c *conn) Send(cmd string, args ...interface{}) error { 519 | c.mu.Lock() 520 | c.pending += 1 521 | c.mu.Unlock() 522 | if c.writeTimeout != 0 { 523 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 524 | } 525 | if err := c.writeCommand(cmd, args); err != nil { 526 | return c.fatal(err) 527 | } 528 | return nil 529 | } 530 | 531 | func (c *conn) Flush() error { 532 | if c.writeTimeout != 0 { 533 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 534 | } 535 | if err := c.bw.Flush(); err != nil { 536 | return c.fatal(err) 537 | } 538 | return nil 539 | } 540 | 541 | func (c *conn) Receive() (reply interface{}, err error) { 542 | if c.readTimeout != 0 { 543 | c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) 544 | } 545 | if reply, err = c.readReply(); err != nil { 546 | return nil, c.fatal(err) 547 | } 548 | // When using pub/sub, the number of receives can be greater than the 549 | // number of sends. To enable normal use of the connection after 550 | // unsubscribing from all channels, we do not decrement pending to a 551 | // negative value. 552 | // 553 | // The pending field is decremented after the reply is read to handle the 554 | // case where Receive is called before Send. 555 | c.mu.Lock() 556 | if c.pending > 0 { 557 | c.pending -= 1 558 | } 559 | c.mu.Unlock() 560 | if err, ok := reply.(Error); ok { 561 | return nil, err 562 | } 563 | return 564 | } 565 | 566 | func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { 567 | c.mu.Lock() 568 | pending := c.pending 569 | c.pending = 0 570 | c.mu.Unlock() 571 | 572 | if cmd == "" && pending == 0 { 573 | return nil, nil 574 | } 575 | 576 | if c.writeTimeout != 0 { 577 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 578 | } 579 | 580 | if cmd != "" { 581 | if err := c.writeCommand(cmd, args); err != nil { 582 | return nil, c.fatal(err) 583 | } 584 | } 585 | 586 | if err := c.bw.Flush(); err != nil { 587 | return nil, c.fatal(err) 588 | } 589 | 590 | if c.readTimeout != 0 { 591 | c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) 592 | } 593 | 594 | if cmd == "" { 595 | reply := make([]interface{}, pending) 596 | for i := range reply { 597 | r, e := c.readReply() 598 | if e != nil { 599 | return nil, c.fatal(e) 600 | } 601 | reply[i] = r 602 | } 603 | return reply, nil 604 | } 605 | 606 | var err error 607 | var reply interface{} 608 | for i := 0; i <= pending; i++ { 609 | var e error 610 | if reply, e = c.readReply(); e != nil { 611 | return nil, c.fatal(e) 612 | } 613 | if e, ok := reply.(Error); ok && err == nil { 614 | err = e 615 | } 616 | } 617 | return reply, err 618 | } 619 | -------------------------------------------------------------------------------- /internal/redigo/redis/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Package redis is a client for the Redis database. 16 | // 17 | // The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more 18 | // documentation about this package. 19 | // 20 | // Connections 21 | // 22 | // The Conn interface is the primary interface for working with Redis. 23 | // Applications create connections by calling the Dial, DialWithTimeout or 24 | // NewConn functions. In the future, functions will be added for creating 25 | // sharded and other types of connections. 26 | // 27 | // The application must call the connection Close method when the application 28 | // is done with the connection. 29 | // 30 | // Executing Commands 31 | // 32 | // The Conn interface has a generic method for executing Redis commands: 33 | // 34 | // Do(commandName string, args ...interface{}) (reply interface{}, err error) 35 | // 36 | // The Redis command reference (http://redis.io/commands) lists the available 37 | // commands. An example of using the Redis APPEND command is: 38 | // 39 | // n, err := conn.Do("APPEND", "key", "value") 40 | // 41 | // The Do method converts command arguments to binary strings for transmission 42 | // to the server as follows: 43 | // 44 | // Go Type Conversion 45 | // []byte Sent as is 46 | // string Sent as is 47 | // int, int64 strconv.FormatInt(v) 48 | // float64 strconv.FormatFloat(v, 'g', -1, 64) 49 | // bool true -> "1", false -> "0" 50 | // nil "" 51 | // all other types fmt.Print(v) 52 | // 53 | // Redis command reply types are represented using the following Go types: 54 | // 55 | // Redis type Go type 56 | // error redis.Error 57 | // integer int64 58 | // simple string string 59 | // bulk string []byte or nil if value not present. 60 | // array []interface{} or nil if value not present. 61 | // 62 | // Use type assertions or the reply helper functions to convert from 63 | // interface{} to the specific Go type for the command result. 64 | // 65 | // Pipelining 66 | // 67 | // Connections support pipelining using the Send, Flush and Receive methods. 68 | // 69 | // Send(commandName string, args ...interface{}) error 70 | // Flush() error 71 | // Receive() (reply interface{}, err error) 72 | // 73 | // Send writes the command to the connection's output buffer. Flush flushes the 74 | // connection's output buffer to the server. Receive reads a single reply from 75 | // the server. The following example shows a simple pipeline. 76 | // 77 | // c.Send("SET", "foo", "bar") 78 | // c.Send("GET", "foo") 79 | // c.Flush() 80 | // c.Receive() // reply from SET 81 | // v, err = c.Receive() // reply from GET 82 | // 83 | // The Do method combines the functionality of the Send, Flush and Receive 84 | // methods. The Do method starts by writing the command and flushing the output 85 | // buffer. Next, the Do method receives all pending replies including the reply 86 | // for the command just sent by Do. If any of the received replies is an error, 87 | // then Do returns the error. If there are no errors, then Do returns the last 88 | // reply. If the command argument to the Do method is "", then the Do method 89 | // will flush the output buffer and receive pending replies without sending a 90 | // command. 91 | // 92 | // Use the Send and Do methods to implement pipelined transactions. 93 | // 94 | // c.Send("MULTI") 95 | // c.Send("INCR", "foo") 96 | // c.Send("INCR", "bar") 97 | // r, err := c.Do("EXEC") 98 | // fmt.Println(r) // prints [1, 1] 99 | // 100 | // Concurrency 101 | // 102 | // Connections support one concurrent caller to the Receive method and one 103 | // concurrent caller to the Send and Flush methods. No other concurrency is 104 | // supported including concurrent calls to the Do method. 105 | // 106 | // For full concurrent access to Redis, use the thread-safe Pool to get, use 107 | // and release a connection from within a goroutine. Connections returned from 108 | // a Pool have the concurrency restrictions described in the previous 109 | // paragraph. 110 | // 111 | // Publish and Subscribe 112 | // 113 | // Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. 114 | // 115 | // c.Send("SUBSCRIBE", "example") 116 | // c.Flush() 117 | // for { 118 | // reply, err := c.Receive() 119 | // if err != nil { 120 | // return err 121 | // } 122 | // // process pushed message 123 | // } 124 | // 125 | // The PubSubConn type wraps a Conn with convenience methods for implementing 126 | // subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods 127 | // send and flush a subscription management command. The receive method 128 | // converts a pushed message to convenient types for use in a type switch. 129 | // 130 | // psc := redis.PubSubConn{Conn: c} 131 | // psc.Subscribe("example") 132 | // for { 133 | // switch v := psc.Receive().(type) { 134 | // case redis.Message: 135 | // fmt.Printf("%s: message: %s\n", v.Channel, v.Data) 136 | // case redis.Subscription: 137 | // fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) 138 | // case error: 139 | // return v 140 | // } 141 | // } 142 | // 143 | // Reply Helpers 144 | // 145 | // The Bool, Int, Bytes, String, Strings and Values functions convert a reply 146 | // to a value of a specific type. To allow convenient wrapping of calls to the 147 | // connection Do and Receive methods, the functions take a second argument of 148 | // type error. If the error is non-nil, then the helper function returns the 149 | // error. If the error is nil, the function converts the reply to the specified 150 | // type: 151 | // 152 | // exists, err := redis.Bool(c.Do("EXISTS", "foo")) 153 | // if err != nil { 154 | // // handle error return from c.Do or type conversion error. 155 | // } 156 | // 157 | // The Scan function converts elements of a array reply to Go types: 158 | // 159 | // var value1 int 160 | // var value2 string 161 | // reply, err := redis.Values(c.Do("MGET", "key1", "key2")) 162 | // if err != nil { 163 | // // handle error 164 | // } 165 | // if _, err := redis.Scan(reply, &value1, &value2); err != nil { 166 | // // handle error 167 | // } 168 | // 169 | // Errors 170 | // 171 | // Connection methods return error replies from the server as type redis.Error. 172 | // 173 | // Call the connection Err() method to determine if the connection encountered 174 | // non-recoverable error such as a network error or protocol parsing error. If 175 | // Err() returns a non-nil value, then the connection is not usable and should 176 | // be closed. 177 | package redis // import "github.com/garyburd/redigo/redis" 178 | -------------------------------------------------------------------------------- /internal/redigo/redis/go17.go: -------------------------------------------------------------------------------- 1 | // +build go1.7 2 | 3 | package redis 4 | 5 | import "crypto/tls" 6 | 7 | // similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case 8 | func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { 9 | if cfg == nil { 10 | return &tls.Config{InsecureSkipVerify: skipVerify} 11 | } 12 | return &tls.Config{ 13 | Rand: cfg.Rand, 14 | Time: cfg.Time, 15 | Certificates: cfg.Certificates, 16 | NameToCertificate: cfg.NameToCertificate, 17 | GetCertificate: cfg.GetCertificate, 18 | RootCAs: cfg.RootCAs, 19 | NextProtos: cfg.NextProtos, 20 | ServerName: cfg.ServerName, 21 | ClientAuth: cfg.ClientAuth, 22 | ClientCAs: cfg.ClientCAs, 23 | InsecureSkipVerify: cfg.InsecureSkipVerify, 24 | CipherSuites: cfg.CipherSuites, 25 | PreferServerCipherSuites: cfg.PreferServerCipherSuites, 26 | ClientSessionCache: cfg.ClientSessionCache, 27 | MinVersion: cfg.MinVersion, 28 | MaxVersion: cfg.MaxVersion, 29 | CurvePreferences: cfg.CurvePreferences, 30 | DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, 31 | Renegotiation: cfg.Renegotiation, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/redigo/redis/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "log" 21 | ) 22 | 23 | // NewLoggingConn returns a logging wrapper around a connection. 24 | func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { 25 | if prefix != "" { 26 | prefix = prefix + "." 27 | } 28 | return &loggingConn{conn, logger, prefix} 29 | } 30 | 31 | type loggingConn struct { 32 | Conn 33 | logger *log.Logger 34 | prefix string 35 | } 36 | 37 | func (c *loggingConn) Close() error { 38 | err := c.Conn.Close() 39 | var buf bytes.Buffer 40 | fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) 41 | c.logger.Output(2, buf.String()) 42 | return err 43 | } 44 | 45 | func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { 46 | const chop = 32 47 | switch v := v.(type) { 48 | case []byte: 49 | if len(v) > chop { 50 | fmt.Fprintf(buf, "%q...", v[:chop]) 51 | } else { 52 | fmt.Fprintf(buf, "%q", v) 53 | } 54 | case string: 55 | if len(v) > chop { 56 | fmt.Fprintf(buf, "%q...", v[:chop]) 57 | } else { 58 | fmt.Fprintf(buf, "%q", v) 59 | } 60 | case []interface{}: 61 | if len(v) == 0 { 62 | buf.WriteString("[]") 63 | } else { 64 | sep := "[" 65 | fin := "]" 66 | if len(v) > chop { 67 | v = v[:chop] 68 | fin = "...]" 69 | } 70 | for _, vv := range v { 71 | buf.WriteString(sep) 72 | c.printValue(buf, vv) 73 | sep = ", " 74 | } 75 | buf.WriteString(fin) 76 | } 77 | default: 78 | fmt.Fprint(buf, v) 79 | } 80 | } 81 | 82 | func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { 83 | var buf bytes.Buffer 84 | fmt.Fprintf(&buf, "%s%s(", c.prefix, method) 85 | if method != "Receive" { 86 | buf.WriteString(commandName) 87 | for _, arg := range args { 88 | buf.WriteString(", ") 89 | c.printValue(&buf, arg) 90 | } 91 | } 92 | buf.WriteString(") -> (") 93 | if method != "Send" { 94 | c.printValue(&buf, reply) 95 | buf.WriteString(", ") 96 | } 97 | fmt.Fprintf(&buf, "%v)", err) 98 | c.logger.Output(3, buf.String()) 99 | } 100 | 101 | func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { 102 | reply, err := c.Conn.Do(commandName, args...) 103 | c.print("Do", commandName, args, reply, err) 104 | return reply, err 105 | } 106 | 107 | func (c *loggingConn) Send(commandName string, args ...interface{}) error { 108 | err := c.Conn.Send(commandName, args...) 109 | c.print("Send", commandName, args, nil, err) 110 | return err 111 | } 112 | 113 | func (c *loggingConn) Receive() (interface{}, error) { 114 | reply, err := c.Conn.Receive() 115 | c.print("Receive", "", nil, reply, err) 116 | return reply, err 117 | } 118 | -------------------------------------------------------------------------------- /internal/redigo/redis/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bytes" 19 | "container/list" 20 | "crypto/rand" 21 | "crypto/sha1" 22 | "errors" 23 | "io" 24 | "strconv" 25 | "sync" 26 | "time" 27 | 28 | "github.com/devfeel/cache/internal/redigo/internal" 29 | ) 30 | 31 | var nowFunc = time.Now // for testing 32 | 33 | // ErrPoolExhausted is returned from a pool connection method (Do, Send, 34 | // Receive, Flush, Err) when the maximum number of database connections in the 35 | // pool has been reached. 36 | var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") 37 | 38 | var ( 39 | errPoolClosed = errors.New("redigo: connection pool closed") 40 | errConnClosed = errors.New("redigo: connection closed") 41 | ) 42 | 43 | // Pool maintains a pool of connections. The application calls the Get method 44 | // to get a connection from the pool and the connection's Close method to 45 | // return the connection's resources to the pool. 46 | // 47 | // The following example shows how to use a pool in a web application. The 48 | // application creates a pool at application startup and makes it available to 49 | // request handlers using a package level variable. The pool configuration used 50 | // here is an example, not a recommendation. 51 | // 52 | // func newPool(addr string) *redis.Pool { 53 | // return &redis.Pool{ 54 | // MaxIdle: 3, 55 | // IdleTimeout: 240 * time.Second, 56 | // Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, 57 | // } 58 | // } 59 | // 60 | // var ( 61 | // pool *redis.Pool 62 | // redisServer = flag.String("redisServer", ":6379", "") 63 | // ) 64 | // 65 | // func main() { 66 | // flag.Parse() 67 | // pool = newPool(*redisServer) 68 | // ... 69 | // } 70 | // 71 | // A request handler gets a connection from the pool and closes the connection 72 | // when the handler is done: 73 | // 74 | // func serveHome(w http.ResponseWriter, r *http.Request) { 75 | // conn := pool.Get() 76 | // defer conn.Close() 77 | // ... 78 | // } 79 | // 80 | // Use the Dial function to authenticate connections with the AUTH command or 81 | // select a database with the SELECT command: 82 | // 83 | // pool := &redis.Pool{ 84 | // // Other pool configuration not shown in this example. 85 | // Dial: func () (redis.Conn, error) { 86 | // c, err := redis.Dial("tcp", server) 87 | // if err != nil { 88 | // return nil, err 89 | // } 90 | // if _, err := c.Do("AUTH", password); err != nil { 91 | // c.Close() 92 | // return nil, err 93 | // } 94 | // if _, err := c.Do("SELECT", db); err != nil { 95 | // c.Close() 96 | // return nil, err 97 | // } 98 | // return c, nil 99 | // } 100 | // } 101 | // 102 | // Use the TestOnBorrow function to check the health of an idle connection 103 | // before the connection is returned to the application. This example PINGs 104 | // connections that have been idle more than a minute: 105 | // 106 | // pool := &redis.Pool{ 107 | // // Other pool configuration not shown in this example. 108 | // TestOnBorrow: func(c redis.Conn, t time.Time) error { 109 | // if time.Since(t) < time.Minute { 110 | // return nil 111 | // } 112 | // _, err := c.Do("PING") 113 | // return err 114 | // }, 115 | // } 116 | // 117 | type Pool struct { 118 | 119 | // Dial is an application supplied function for creating and configuring a 120 | // connection. 121 | // 122 | // The connection returned from Dial must not be in a special state 123 | // (subscribed to pubsub channel, transaction started, ...). 124 | Dial func() (Conn, error) 125 | 126 | // TestOnBorrow is an optional application supplied function for checking 127 | // the health of an idle connection before the connection is used again by 128 | // the application. Argument t is the time that the connection was returned 129 | // to the pool. If the function returns an error, then the connection is 130 | // closed. 131 | TestOnBorrow func(c Conn, t time.Time) error 132 | 133 | // Maximum number of idle connections in the pool. 134 | MaxIdle int 135 | 136 | // Maximum number of connections allocated by the pool at a given time. 137 | // When zero, there is no limit on the number of connections in the pool. 138 | MaxActive int 139 | 140 | // Close connections after remaining idle for this duration. If the value 141 | // is zero, then idle connections are not closed. Applications should set 142 | // the timeout to a value less than the server's timeout. 143 | IdleTimeout time.Duration 144 | 145 | // If Wait is true and the pool is at the MaxActive limit, then Get() waits 146 | // for a connection to be returned to the pool before returning. 147 | Wait bool 148 | 149 | // mu protects fields defined below. 150 | mu sync.Mutex 151 | cond *sync.Cond 152 | closed bool 153 | active int 154 | 155 | // Stack of idleConn with most recently used at the front. 156 | idle list.List 157 | } 158 | 159 | type idleConn struct { 160 | c Conn 161 | t time.Time 162 | } 163 | 164 | // NewPool creates a new pool. 165 | // 166 | // Deprecated: Initialize the Pool directory as shown in the example. 167 | func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { 168 | return &Pool{Dial: newFn, MaxIdle: maxIdle} 169 | } 170 | 171 | // Get gets a connection. The application must close the returned connection. 172 | // This method always returns a valid connection so that applications can defer 173 | // error handling to the first use of the connection. If there is an error 174 | // getting an underlying connection, then the connection Err, Do, Send, Flush 175 | // and Receive methods return that error. 176 | func (p *Pool) Get() Conn { 177 | c, err := p.get() 178 | if err != nil { 179 | return errorConnection{err} 180 | } 181 | return &pooledConnection{p: p, c: c} 182 | } 183 | 184 | // ActiveCount returns the number of active connections in the pool. 185 | func (p *Pool) ActiveCount() int { 186 | p.mu.Lock() 187 | active := p.active 188 | p.mu.Unlock() 189 | return active 190 | } 191 | 192 | // Close releases the resources used by the pool. 193 | func (p *Pool) Close() error { 194 | p.mu.Lock() 195 | idle := p.idle 196 | p.idle.Init() 197 | p.closed = true 198 | p.active -= idle.Len() 199 | if p.cond != nil { 200 | p.cond.Broadcast() 201 | } 202 | p.mu.Unlock() 203 | for e := idle.Front(); e != nil; e = e.Next() { 204 | e.Value.(idleConn).c.Close() 205 | } 206 | return nil 207 | } 208 | 209 | // release decrements the active count and signals waiters. The caller must 210 | // hold p.mu during the call. 211 | func (p *Pool) release() { 212 | p.active -= 1 213 | if p.cond != nil { 214 | p.cond.Signal() 215 | } 216 | } 217 | 218 | // get prunes stale connections and returns a connection from the idle list or 219 | // creates a new connection. 220 | func (p *Pool) get() (Conn, error) { 221 | p.mu.Lock() 222 | 223 | // Prune stale connections. 224 | 225 | if timeout := p.IdleTimeout; timeout > 0 { 226 | for i, n := 0, p.idle.Len(); i < n; i++ { 227 | e := p.idle.Back() 228 | if e == nil { 229 | break 230 | } 231 | ic := e.Value.(idleConn) 232 | if ic.t.Add(timeout).After(nowFunc()) { 233 | break 234 | } 235 | p.idle.Remove(e) 236 | p.release() 237 | p.mu.Unlock() 238 | ic.c.Close() 239 | p.mu.Lock() 240 | } 241 | } 242 | 243 | for { 244 | 245 | // Get idle connection. 246 | 247 | for i, n := 0, p.idle.Len(); i < n; i++ { 248 | e := p.idle.Front() 249 | if e == nil { 250 | break 251 | } 252 | ic := e.Value.(idleConn) 253 | p.idle.Remove(e) 254 | test := p.TestOnBorrow 255 | p.mu.Unlock() 256 | if test == nil || test(ic.c, ic.t) == nil { 257 | return ic.c, nil 258 | } 259 | ic.c.Close() 260 | p.mu.Lock() 261 | p.release() 262 | } 263 | 264 | // Check for pool closed before dialing a new connection. 265 | 266 | if p.closed { 267 | p.mu.Unlock() 268 | return nil, errors.New("redigo: get on closed pool") 269 | } 270 | 271 | // Dial new connection if under limit. 272 | 273 | if p.MaxActive == 0 || p.active < p.MaxActive { 274 | dial := p.Dial 275 | p.active += 1 276 | p.mu.Unlock() 277 | c, err := dial() 278 | if err != nil { 279 | p.mu.Lock() 280 | p.release() 281 | p.mu.Unlock() 282 | c = nil 283 | } 284 | return c, err 285 | } 286 | 287 | if !p.Wait { 288 | p.mu.Unlock() 289 | return nil, ErrPoolExhausted 290 | } 291 | 292 | if p.cond == nil { 293 | p.cond = sync.NewCond(&p.mu) 294 | } 295 | p.cond.Wait() 296 | } 297 | } 298 | 299 | func (p *Pool) put(c Conn, forceClose bool) error { 300 | err := c.Err() 301 | p.mu.Lock() 302 | if !p.closed && err == nil && !forceClose { 303 | p.idle.PushFront(idleConn{t: nowFunc(), c: c}) 304 | if p.idle.Len() > p.MaxIdle { 305 | c = p.idle.Remove(p.idle.Back()).(idleConn).c 306 | } else { 307 | c = nil 308 | } 309 | } 310 | 311 | if c == nil { 312 | if p.cond != nil { 313 | p.cond.Signal() 314 | } 315 | p.mu.Unlock() 316 | return nil 317 | } 318 | 319 | p.release() 320 | p.mu.Unlock() 321 | return c.Close() 322 | } 323 | 324 | type pooledConnection struct { 325 | p *Pool 326 | c Conn 327 | state int 328 | } 329 | 330 | var ( 331 | sentinel []byte 332 | sentinelOnce sync.Once 333 | ) 334 | 335 | func initSentinel() { 336 | p := make([]byte, 64) 337 | if _, err := rand.Read(p); err == nil { 338 | sentinel = p 339 | } else { 340 | h := sha1.New() 341 | io.WriteString(h, "Oops, rand failed. Use time instead.") 342 | io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) 343 | sentinel = h.Sum(nil) 344 | } 345 | } 346 | 347 | func (pc *pooledConnection) Close() error { 348 | c := pc.c 349 | if _, ok := c.(errorConnection); ok { 350 | return nil 351 | } 352 | pc.c = errorConnection{errConnClosed} 353 | 354 | if pc.state&internal.MultiState != 0 { 355 | c.Send("DISCARD") 356 | pc.state &^= (internal.MultiState | internal.WatchState) 357 | } else if pc.state&internal.WatchState != 0 { 358 | c.Send("UNWATCH") 359 | pc.state &^= internal.WatchState 360 | } 361 | if pc.state&internal.SubscribeState != 0 { 362 | c.Send("UNSUBSCRIBE") 363 | c.Send("PUNSUBSCRIBE") 364 | // To detect the end of the message stream, ask the server to echo 365 | // a sentinel value and read until we see that value. 366 | sentinelOnce.Do(initSentinel) 367 | c.Send("ECHO", sentinel) 368 | c.Flush() 369 | for { 370 | p, err := c.Receive() 371 | if err != nil { 372 | break 373 | } 374 | if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { 375 | pc.state &^= internal.SubscribeState 376 | break 377 | } 378 | } 379 | } 380 | c.Do("") 381 | pc.p.put(c, pc.state != 0) 382 | return nil 383 | } 384 | 385 | func (pc *pooledConnection) Err() error { 386 | return pc.c.Err() 387 | } 388 | 389 | func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { 390 | ci := internal.LookupCommandInfo(commandName) 391 | pc.state = (pc.state | ci.Set) &^ ci.Clear 392 | return pc.c.Do(commandName, args...) 393 | } 394 | 395 | func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { 396 | ci := internal.LookupCommandInfo(commandName) 397 | pc.state = (pc.state | ci.Set) &^ ci.Clear 398 | return pc.c.Send(commandName, args...) 399 | } 400 | 401 | func (pc *pooledConnection) Flush() error { 402 | return pc.c.Flush() 403 | } 404 | 405 | func (pc *pooledConnection) Receive() (reply interface{}, err error) { 406 | return pc.c.Receive() 407 | } 408 | 409 | type errorConnection struct{ err error } 410 | 411 | func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } 412 | func (ec errorConnection) Send(string, ...interface{}) error { return ec.err } 413 | func (ec errorConnection) Err() error { return ec.err } 414 | func (ec errorConnection) Close() error { return ec.err } 415 | func (ec errorConnection) Flush() error { return ec.err } 416 | func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err } 417 | -------------------------------------------------------------------------------- /internal/redigo/redis/pre_go17.go: -------------------------------------------------------------------------------- 1 | // +build !go1.7 2 | 3 | package redis 4 | 5 | import "crypto/tls" 6 | 7 | // similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case 8 | func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { 9 | if cfg == nil { 10 | return &tls.Config{InsecureSkipVerify: skipVerify} 11 | } 12 | return &tls.Config{ 13 | Rand: cfg.Rand, 14 | Time: cfg.Time, 15 | Certificates: cfg.Certificates, 16 | NameToCertificate: cfg.NameToCertificate, 17 | GetCertificate: cfg.GetCertificate, 18 | RootCAs: cfg.RootCAs, 19 | NextProtos: cfg.NextProtos, 20 | ServerName: cfg.ServerName, 21 | ClientAuth: cfg.ClientAuth, 22 | ClientCAs: cfg.ClientCAs, 23 | InsecureSkipVerify: cfg.InsecureSkipVerify, 24 | CipherSuites: cfg.CipherSuites, 25 | PreferServerCipherSuites: cfg.PreferServerCipherSuites, 26 | ClientSessionCache: cfg.ClientSessionCache, 27 | MinVersion: cfg.MinVersion, 28 | MaxVersion: cfg.MaxVersion, 29 | CurvePreferences: cfg.CurvePreferences, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/redigo/redis/pubsub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import "errors" 18 | 19 | // Subscription represents a subscribe or unsubscribe notification. 20 | type Subscription struct { 21 | 22 | // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" 23 | Kind string 24 | 25 | // The channel that was changed. 26 | Channel string 27 | 28 | // The current number of subscriptions for connection. 29 | Count int 30 | } 31 | 32 | // Message represents a message notification. 33 | type Message struct { 34 | 35 | // The originating channel. 36 | Channel string 37 | 38 | // The message data. 39 | Data []byte 40 | } 41 | 42 | // PMessage represents a pmessage notification. 43 | type PMessage struct { 44 | 45 | // The matched pattern. 46 | Pattern string 47 | 48 | // The originating channel. 49 | Channel string 50 | 51 | // The message data. 52 | Data []byte 53 | } 54 | 55 | // Pong represents a pubsub pong notification. 56 | type Pong struct { 57 | Data string 58 | } 59 | 60 | // PubSubConn wraps a Conn with convenience methods for subscribers. 61 | type PubSubConn struct { 62 | Conn Conn 63 | } 64 | 65 | // Close closes the connection. 66 | func (c PubSubConn) Close() error { 67 | return c.Conn.Close() 68 | } 69 | 70 | // Subscribe subscribes the connection to the specified channels. 71 | func (c PubSubConn) Subscribe(channel ...interface{}) error { 72 | c.Conn.Send("SUBSCRIBE", channel...) 73 | return c.Conn.Flush() 74 | } 75 | 76 | // PSubscribe subscribes the connection to the given patterns. 77 | func (c PubSubConn) PSubscribe(channel ...interface{}) error { 78 | c.Conn.Send("PSUBSCRIBE", channel...) 79 | return c.Conn.Flush() 80 | } 81 | 82 | // Unsubscribe unsubscribes the connection from the given channels, or from all 83 | // of them if none is given. 84 | func (c PubSubConn) Unsubscribe(channel ...interface{}) error { 85 | c.Conn.Send("UNSUBSCRIBE", channel...) 86 | return c.Conn.Flush() 87 | } 88 | 89 | // PUnsubscribe unsubscribes the connection from the given patterns, or from all 90 | // of them if none is given. 91 | func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { 92 | c.Conn.Send("PUNSUBSCRIBE", channel...) 93 | return c.Conn.Flush() 94 | } 95 | 96 | // Ping sends a PING to the server with the specified data. 97 | func (c PubSubConn) Ping(data string) error { 98 | c.Conn.Send("PING", data) 99 | return c.Conn.Flush() 100 | } 101 | 102 | // Receive returns a pushed message as a Subscription, Message, PMessage, Pong 103 | // or error. The return value is intended to be used directly in a type switch 104 | // as illustrated in the PubSubConn example. 105 | func (c PubSubConn) Receive() interface{} { 106 | reply, err := Values(c.Conn.Receive()) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | var kind string 112 | reply, err = Scan(reply, &kind) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | switch kind { 118 | case "message": 119 | var m Message 120 | if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { 121 | return err 122 | } 123 | return m 124 | case "pmessage": 125 | var pm PMessage 126 | if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { 127 | return err 128 | } 129 | return pm 130 | case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": 131 | s := Subscription{Kind: kind} 132 | if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { 133 | return err 134 | } 135 | return s 136 | case "pong": 137 | var p Pong 138 | if _, err := Scan(reply, &p.Data); err != nil { 139 | return err 140 | } 141 | return p 142 | } 143 | return errors.New("redigo: unknown pubsub notification") 144 | } 145 | -------------------------------------------------------------------------------- /internal/redigo/redis/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | // Error represents an error returned in a command reply. 18 | type Error string 19 | 20 | func (err Error) Error() string { return string(err) } 21 | 22 | // Conn represents a connection to a Redis server. 23 | type Conn interface { 24 | // Close closes the connection. 25 | Close() error 26 | 27 | // Err returns a non-nil value when the connection is not usable. 28 | Err() error 29 | 30 | // Do sends a command to the server and returns the received reply. 31 | Do(commandName string, args ...interface{}) (reply interface{}, err error) 32 | 33 | // Send writes the command to the client's output buffer. 34 | Send(commandName string, args ...interface{}) error 35 | 36 | // Flush flushes the output buffer to the Redis server. 37 | Flush() error 38 | 39 | // Receive receives a single reply from the Redis server 40 | Receive() (reply interface{}, err error) 41 | } 42 | -------------------------------------------------------------------------------- /internal/redigo/redis/reply.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "strconv" 21 | ) 22 | 23 | // ErrNil indicates that a reply value is nil. 24 | var ErrNil = errors.New("redigo: nil returned") 25 | 26 | // Int is a helper that converts a command reply to an integer. If err is not 27 | // equal to nil, then Int returns 0, err. Otherwise, Int converts the 28 | // reply to an int as follows: 29 | // 30 | // Reply type Result 31 | // integer int(reply), nil 32 | // bulk string parsed reply, nil 33 | // nil 0, ErrNil 34 | // other 0, error 35 | func Int(reply interface{}, err error) (int, error) { 36 | if err != nil { 37 | return 0, err 38 | } 39 | switch reply := reply.(type) { 40 | case int64: 41 | x := int(reply) 42 | if int64(x) != reply { 43 | return 0, strconv.ErrRange 44 | } 45 | return x, nil 46 | case []byte: 47 | n, err := strconv.ParseInt(string(reply), 10, 0) 48 | return int(n), err 49 | case nil: 50 | return 0, ErrNil 51 | case Error: 52 | return 0, reply 53 | } 54 | return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) 55 | } 56 | 57 | // Int64 is a helper that converts a command reply to 64 bit integer. If err is 58 | // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the 59 | // reply to an int64 as follows: 60 | // 61 | // Reply type Result 62 | // integer reply, nil 63 | // bulk string parsed reply, nil 64 | // nil 0, ErrNil 65 | // other 0, error 66 | func Int64(reply interface{}, err error) (int64, error) { 67 | if err != nil { 68 | return 0, err 69 | } 70 | switch reply := reply.(type) { 71 | case int64: 72 | return reply, nil 73 | case []byte: 74 | n, err := strconv.ParseInt(string(reply), 10, 64) 75 | return n, err 76 | case nil: 77 | return 0, ErrNil 78 | case Error: 79 | return 0, reply 80 | } 81 | return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) 82 | } 83 | 84 | var errNegativeInt = errors.New("redigo: unexpected value for Uint64") 85 | 86 | // Uint64 is a helper that converts a command reply to 64 bit integer. If err is 87 | // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the 88 | // reply to an int64 as follows: 89 | // 90 | // Reply type Result 91 | // integer reply, nil 92 | // bulk string parsed reply, nil 93 | // nil 0, ErrNil 94 | // other 0, error 95 | func Uint64(reply interface{}, err error) (uint64, error) { 96 | if err != nil { 97 | return 0, err 98 | } 99 | switch reply := reply.(type) { 100 | case int64: 101 | if reply < 0 { 102 | return 0, errNegativeInt 103 | } 104 | return uint64(reply), nil 105 | case []byte: 106 | n, err := strconv.ParseUint(string(reply), 10, 64) 107 | return n, err 108 | case nil: 109 | return 0, ErrNil 110 | case Error: 111 | return 0, reply 112 | } 113 | return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) 114 | } 115 | 116 | // Float64 is a helper that converts a command reply to 64 bit float. If err is 117 | // not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts 118 | // the reply to an int as follows: 119 | // 120 | // Reply type Result 121 | // bulk string parsed reply, nil 122 | // nil 0, ErrNil 123 | // other 0, error 124 | func Float64(reply interface{}, err error) (float64, error) { 125 | if err != nil { 126 | return 0, err 127 | } 128 | switch reply := reply.(type) { 129 | case []byte: 130 | n, err := strconv.ParseFloat(string(reply), 64) 131 | return n, err 132 | case nil: 133 | return 0, ErrNil 134 | case Error: 135 | return 0, reply 136 | } 137 | return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) 138 | } 139 | 140 | // String is a helper that converts a command reply to a string. If err is not 141 | // equal to nil, then String returns "", err. Otherwise String converts the 142 | // reply to a string as follows: 143 | // 144 | // Reply type Result 145 | // bulk string string(reply), nil 146 | // simple string reply, nil 147 | // nil "", ErrNil 148 | // other "", error 149 | func String(reply interface{}, err error) (string, error) { 150 | if err != nil { 151 | return "", err 152 | } 153 | switch reply := reply.(type) { 154 | case []byte: 155 | return string(reply), nil 156 | case string: 157 | return reply, nil 158 | case nil: 159 | return "", ErrNil 160 | case Error: 161 | return "", reply 162 | } 163 | return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) 164 | } 165 | 166 | // Bytes is a helper that converts a command reply to a slice of bytes. If err 167 | // is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts 168 | // the reply to a slice of bytes as follows: 169 | // 170 | // Reply type Result 171 | // bulk string reply, nil 172 | // simple string []byte(reply), nil 173 | // nil nil, ErrNil 174 | // other nil, error 175 | func Bytes(reply interface{}, err error) ([]byte, error) { 176 | if err != nil { 177 | return nil, err 178 | } 179 | switch reply := reply.(type) { 180 | case []byte: 181 | return reply, nil 182 | case string: 183 | return []byte(reply), nil 184 | case nil: 185 | return nil, ErrNil 186 | case Error: 187 | return nil, reply 188 | } 189 | return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) 190 | } 191 | 192 | // Bool is a helper that converts a command reply to a boolean. If err is not 193 | // equal to nil, then Bool returns false, err. Otherwise Bool converts the 194 | // reply to boolean as follows: 195 | // 196 | // Reply type Result 197 | // integer value != 0, nil 198 | // bulk string strconv.ParseBool(reply) 199 | // nil false, ErrNil 200 | // other false, error 201 | func Bool(reply interface{}, err error) (bool, error) { 202 | if err != nil { 203 | return false, err 204 | } 205 | switch reply := reply.(type) { 206 | case int64: 207 | return reply != 0, nil 208 | case []byte: 209 | return strconv.ParseBool(string(reply)) 210 | case nil: 211 | return false, ErrNil 212 | case Error: 213 | return false, reply 214 | } 215 | return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) 216 | } 217 | 218 | // MultiBulk is a helper that converts an array command reply to a []interface{}. 219 | // 220 | // Deprecated: Use Values instead. 221 | func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } 222 | 223 | // Values is a helper that converts an array command reply to a []interface{}. 224 | // If err is not equal to nil, then Values returns nil, err. Otherwise, Values 225 | // converts the reply as follows: 226 | // 227 | // Reply type Result 228 | // array reply, nil 229 | // nil nil, ErrNil 230 | // other nil, error 231 | func Values(reply interface{}, err error) ([]interface{}, error) { 232 | if err != nil { 233 | return nil, err 234 | } 235 | switch reply := reply.(type) { 236 | case []interface{}: 237 | return reply, nil 238 | case nil: 239 | return nil, ErrNil 240 | case Error: 241 | return nil, reply 242 | } 243 | return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) 244 | } 245 | 246 | // Strings is a helper that converts an array command reply to a []string. If 247 | // err is not equal to nil, then Strings returns nil, err. Nil array items are 248 | // converted to "" in the output slice. Strings returns an error if an array 249 | // item is not a bulk string or nil. 250 | func Strings(reply interface{}, err error) ([]string, error) { 251 | if err != nil { 252 | return nil, err 253 | } 254 | switch reply := reply.(type) { 255 | case []interface{}: 256 | result := make([]string, len(reply)) 257 | for i := range reply { 258 | if reply[i] == nil { 259 | continue 260 | } 261 | p, ok := reply[i].([]byte) 262 | if !ok { 263 | return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i]) 264 | } 265 | result[i] = string(p) 266 | } 267 | return result, nil 268 | case nil: 269 | return nil, ErrNil 270 | case Error: 271 | return nil, reply 272 | } 273 | return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply) 274 | } 275 | 276 | // ByteSlices is a helper that converts an array command reply to a [][]byte. 277 | // If err is not equal to nil, then ByteSlices returns nil, err. Nil array 278 | // items are stay nil. ByteSlices returns an error if an array item is not a 279 | // bulk string or nil. 280 | func ByteSlices(reply interface{}, err error) ([][]byte, error) { 281 | if err != nil { 282 | return nil, err 283 | } 284 | switch reply := reply.(type) { 285 | case []interface{}: 286 | result := make([][]byte, len(reply)) 287 | for i := range reply { 288 | if reply[i] == nil { 289 | continue 290 | } 291 | p, ok := reply[i].([]byte) 292 | if !ok { 293 | return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i]) 294 | } 295 | result[i] = p 296 | } 297 | return result, nil 298 | case nil: 299 | return nil, ErrNil 300 | case Error: 301 | return nil, reply 302 | } 303 | return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply) 304 | } 305 | 306 | // Ints is a helper that converts an array command reply to a []int. If 307 | // err is not equal to nil, then Ints returns nil, err. 308 | func Ints(reply interface{}, err error) ([]int, error) { 309 | var ints []int 310 | values, err := Values(reply, err) 311 | if err != nil { 312 | return ints, err 313 | } 314 | if err := ScanSlice(values, &ints); err != nil { 315 | return ints, err 316 | } 317 | return ints, nil 318 | } 319 | 320 | // StringMap is a helper that converts an array of strings (alternating key, value) 321 | // into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. 322 | // Requires an even number of values in result. 323 | func StringMap(result interface{}, err error) (map[string]string, error) { 324 | values, err := Values(result, err) 325 | if err != nil { 326 | return nil, err 327 | } 328 | if len(values)%2 != 0 { 329 | return nil, errors.New("redigo: StringMap expects even number of values result") 330 | } 331 | m := make(map[string]string, len(values)/2) 332 | for i := 0; i < len(values); i += 2 { 333 | key, okKey := values[i].([]byte) 334 | value, okValue := values[i+1].([]byte) 335 | if !okKey || !okValue { 336 | return nil, errors.New("redigo: ScanMap key not a bulk string value") 337 | } 338 | m[string(key)] = string(value) 339 | } 340 | return m, nil 341 | } 342 | 343 | // IntMap is a helper that converts an array of strings (alternating key, value) 344 | // into a map[string]int. The HGETALL commands return replies in this format. 345 | // Requires an even number of values in result. 346 | func IntMap(result interface{}, err error) (map[string]int, error) { 347 | values, err := Values(result, err) 348 | if err != nil { 349 | return nil, err 350 | } 351 | if len(values)%2 != 0 { 352 | return nil, errors.New("redigo: IntMap expects even number of values result") 353 | } 354 | m := make(map[string]int, len(values)/2) 355 | for i := 0; i < len(values); i += 2 { 356 | key, ok := values[i].([]byte) 357 | if !ok { 358 | return nil, errors.New("redigo: ScanMap key not a bulk string value") 359 | } 360 | value, err := Int(values[i+1], nil) 361 | if err != nil { 362 | return nil, err 363 | } 364 | m[string(key)] = value 365 | } 366 | return m, nil 367 | } 368 | 369 | // Int64Map is a helper that converts an array of strings (alternating key, value) 370 | // into a map[string]int64. The HGETALL commands return replies in this format. 371 | // Requires an even number of values in result. 372 | func Int64Map(result interface{}, err error) (map[string]int64, error) { 373 | values, err := Values(result, err) 374 | if err != nil { 375 | return nil, err 376 | } 377 | if len(values)%2 != 0 { 378 | return nil, errors.New("redigo: Int64Map expects even number of values result") 379 | } 380 | m := make(map[string]int64, len(values)/2) 381 | for i := 0; i < len(values); i += 2 { 382 | key, ok := values[i].([]byte) 383 | if !ok { 384 | return nil, errors.New("redigo: ScanMap key not a bulk string value") 385 | } 386 | value, err := Int64(values[i+1], nil) 387 | if err != nil { 388 | return nil, err 389 | } 390 | m[string(key)] = value 391 | } 392 | return m, nil 393 | } 394 | -------------------------------------------------------------------------------- /internal/redigo/redis/scan.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "reflect" 21 | "strconv" 22 | "strings" 23 | "sync" 24 | ) 25 | 26 | func ensureLen(d reflect.Value, n int) { 27 | if n > d.Cap() { 28 | d.Set(reflect.MakeSlice(d.Type(), n, n)) 29 | } else { 30 | d.SetLen(n) 31 | } 32 | } 33 | 34 | func cannotConvert(d reflect.Value, s interface{}) error { 35 | var sname string 36 | switch s.(type) { 37 | case string: 38 | sname = "Redis simple string" 39 | case Error: 40 | sname = "Redis error" 41 | case int64: 42 | sname = "Redis integer" 43 | case []byte: 44 | sname = "Redis bulk string" 45 | case []interface{}: 46 | sname = "Redis array" 47 | default: 48 | sname = reflect.TypeOf(s).String() 49 | } 50 | return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) 51 | } 52 | 53 | func convertAssignBulkString(d reflect.Value, s []byte) (err error) { 54 | switch d.Type().Kind() { 55 | case reflect.Float32, reflect.Float64: 56 | var x float64 57 | x, err = strconv.ParseFloat(string(s), d.Type().Bits()) 58 | d.SetFloat(x) 59 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 60 | var x int64 61 | x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) 62 | d.SetInt(x) 63 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 64 | var x uint64 65 | x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) 66 | d.SetUint(x) 67 | case reflect.Bool: 68 | var x bool 69 | x, err = strconv.ParseBool(string(s)) 70 | d.SetBool(x) 71 | case reflect.String: 72 | d.SetString(string(s)) 73 | case reflect.Slice: 74 | if d.Type().Elem().Kind() != reflect.Uint8 { 75 | err = cannotConvert(d, s) 76 | } else { 77 | d.SetBytes(s) 78 | } 79 | default: 80 | err = cannotConvert(d, s) 81 | } 82 | return 83 | } 84 | 85 | func convertAssignInt(d reflect.Value, s int64) (err error) { 86 | switch d.Type().Kind() { 87 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 88 | d.SetInt(s) 89 | if d.Int() != s { 90 | err = strconv.ErrRange 91 | d.SetInt(0) 92 | } 93 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 94 | if s < 0 { 95 | err = strconv.ErrRange 96 | } else { 97 | x := uint64(s) 98 | d.SetUint(x) 99 | if d.Uint() != x { 100 | err = strconv.ErrRange 101 | d.SetUint(0) 102 | } 103 | } 104 | case reflect.Bool: 105 | d.SetBool(s != 0) 106 | default: 107 | err = cannotConvert(d, s) 108 | } 109 | return 110 | } 111 | 112 | func convertAssignValue(d reflect.Value, s interface{}) (err error) { 113 | switch s := s.(type) { 114 | case []byte: 115 | err = convertAssignBulkString(d, s) 116 | case int64: 117 | err = convertAssignInt(d, s) 118 | default: 119 | err = cannotConvert(d, s) 120 | } 121 | return err 122 | } 123 | 124 | func convertAssignArray(d reflect.Value, s []interface{}) error { 125 | if d.Type().Kind() != reflect.Slice { 126 | return cannotConvert(d, s) 127 | } 128 | ensureLen(d, len(s)) 129 | for i := 0; i < len(s); i++ { 130 | if err := convertAssignValue(d.Index(i), s[i]); err != nil { 131 | return err 132 | } 133 | } 134 | return nil 135 | } 136 | 137 | func convertAssign(d interface{}, s interface{}) (err error) { 138 | // Handle the most common destination types using type switches and 139 | // fall back to reflection for all other types. 140 | switch s := s.(type) { 141 | case nil: 142 | // ingore 143 | case []byte: 144 | switch d := d.(type) { 145 | case *string: 146 | *d = string(s) 147 | case *int: 148 | *d, err = strconv.Atoi(string(s)) 149 | case *bool: 150 | *d, err = strconv.ParseBool(string(s)) 151 | case *[]byte: 152 | *d = s 153 | case *interface{}: 154 | *d = s 155 | case nil: 156 | // skip value 157 | default: 158 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 159 | err = cannotConvert(d, s) 160 | } else { 161 | err = convertAssignBulkString(d.Elem(), s) 162 | } 163 | } 164 | case int64: 165 | switch d := d.(type) { 166 | case *int: 167 | x := int(s) 168 | if int64(x) != s { 169 | err = strconv.ErrRange 170 | x = 0 171 | } 172 | *d = x 173 | case *bool: 174 | *d = s != 0 175 | case *interface{}: 176 | *d = s 177 | case nil: 178 | // skip value 179 | default: 180 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 181 | err = cannotConvert(d, s) 182 | } else { 183 | err = convertAssignInt(d.Elem(), s) 184 | } 185 | } 186 | case string: 187 | switch d := d.(type) { 188 | case *string: 189 | *d = string(s) 190 | default: 191 | err = cannotConvert(reflect.ValueOf(d), s) 192 | } 193 | case []interface{}: 194 | switch d := d.(type) { 195 | case *[]interface{}: 196 | *d = s 197 | case *interface{}: 198 | *d = s 199 | case nil: 200 | // skip value 201 | default: 202 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 203 | err = cannotConvert(d, s) 204 | } else { 205 | err = convertAssignArray(d.Elem(), s) 206 | } 207 | } 208 | case Error: 209 | err = s 210 | default: 211 | err = cannotConvert(reflect.ValueOf(d), s) 212 | } 213 | return 214 | } 215 | 216 | // Scan copies from src to the values pointed at by dest. 217 | // 218 | // The values pointed at by dest must be an integer, float, boolean, string, 219 | // []byte, interface{} or slices of these types. Scan uses the standard strconv 220 | // package to convert bulk strings to numeric and boolean types. 221 | // 222 | // If a dest value is nil, then the corresponding src value is skipped. 223 | // 224 | // If a src element is nil, then the corresponding dest value is not modified. 225 | // 226 | // To enable easy use of Scan in a loop, Scan returns the slice of src 227 | // following the copied values. 228 | func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { 229 | if len(src) < len(dest) { 230 | return nil, errors.New("redigo.Scan: array short") 231 | } 232 | var err error 233 | for i, d := range dest { 234 | err = convertAssign(d, src[i]) 235 | if err != nil { 236 | err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) 237 | break 238 | } 239 | } 240 | return src[len(dest):], err 241 | } 242 | 243 | type fieldSpec struct { 244 | name string 245 | index []int 246 | omitEmpty bool 247 | } 248 | 249 | type structSpec struct { 250 | m map[string]*fieldSpec 251 | l []*fieldSpec 252 | } 253 | 254 | func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { 255 | return ss.m[string(name)] 256 | } 257 | 258 | func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { 259 | for i := 0; i < t.NumField(); i++ { 260 | f := t.Field(i) 261 | switch { 262 | case f.PkgPath != "" && !f.Anonymous: 263 | // Ignore unexported fields. 264 | case f.Anonymous: 265 | // TODO: Handle pointers. Requires change to decoder and 266 | // protection against infinite recursion. 267 | if f.Type.Kind() == reflect.Struct { 268 | compileStructSpec(f.Type, depth, append(index, i), ss) 269 | } 270 | default: 271 | fs := &fieldSpec{name: f.Name} 272 | tag := f.Tag.Get("redis") 273 | p := strings.Split(tag, ",") 274 | if len(p) > 0 { 275 | if p[0] == "-" { 276 | continue 277 | } 278 | if len(p[0]) > 0 { 279 | fs.name = p[0] 280 | } 281 | for _, s := range p[1:] { 282 | switch s { 283 | case "omitempty": 284 | fs.omitEmpty = true 285 | default: 286 | panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) 287 | } 288 | } 289 | } 290 | d, found := depth[fs.name] 291 | if !found { 292 | d = 1 << 30 293 | } 294 | switch { 295 | case len(index) == d: 296 | // At same depth, remove from result. 297 | delete(ss.m, fs.name) 298 | j := 0 299 | for i := 0; i < len(ss.l); i++ { 300 | if fs.name != ss.l[i].name { 301 | ss.l[j] = ss.l[i] 302 | j += 1 303 | } 304 | } 305 | ss.l = ss.l[:j] 306 | case len(index) < d: 307 | fs.index = make([]int, len(index)+1) 308 | copy(fs.index, index) 309 | fs.index[len(index)] = i 310 | depth[fs.name] = len(index) 311 | ss.m[fs.name] = fs 312 | ss.l = append(ss.l, fs) 313 | } 314 | } 315 | } 316 | } 317 | 318 | var ( 319 | structSpecMutex sync.RWMutex 320 | structSpecCache = make(map[reflect.Type]*structSpec) 321 | defaultFieldSpec = &fieldSpec{} 322 | ) 323 | 324 | func structSpecForType(t reflect.Type) *structSpec { 325 | 326 | structSpecMutex.RLock() 327 | ss, found := structSpecCache[t] 328 | structSpecMutex.RUnlock() 329 | if found { 330 | return ss 331 | } 332 | 333 | structSpecMutex.Lock() 334 | defer structSpecMutex.Unlock() 335 | ss, found = structSpecCache[t] 336 | if found { 337 | return ss 338 | } 339 | 340 | ss = &structSpec{m: make(map[string]*fieldSpec)} 341 | compileStructSpec(t, make(map[string]int), nil, ss) 342 | structSpecCache[t] = ss 343 | return ss 344 | } 345 | 346 | var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") 347 | 348 | // ScanStruct scans alternating names and values from src to a struct. The 349 | // HGETALL and CONFIG GET commands return replies in this format. 350 | // 351 | // ScanStruct uses exported field names to match values in the response. Use 352 | // 'redis' field tag to override the name: 353 | // 354 | // Field int `redis:"myName"` 355 | // 356 | // Fields with the tag redis:"-" are ignored. 357 | // 358 | // Integer, float, boolean, string and []byte fields are supported. Scan uses the 359 | // standard strconv package to convert bulk string values to numeric and 360 | // boolean types. 361 | // 362 | // If a src element is nil, then the corresponding field is not modified. 363 | func ScanStruct(src []interface{}, dest interface{}) error { 364 | d := reflect.ValueOf(dest) 365 | if d.Kind() != reflect.Ptr || d.IsNil() { 366 | return errScanStructValue 367 | } 368 | d = d.Elem() 369 | if d.Kind() != reflect.Struct { 370 | return errScanStructValue 371 | } 372 | ss := structSpecForType(d.Type()) 373 | 374 | if len(src)%2 != 0 { 375 | return errors.New("redigo.ScanStruct: number of values not a multiple of 2") 376 | } 377 | 378 | for i := 0; i < len(src); i += 2 { 379 | s := src[i+1] 380 | if s == nil { 381 | continue 382 | } 383 | name, ok := src[i].([]byte) 384 | if !ok { 385 | return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) 386 | } 387 | fs := ss.fieldSpec(name) 388 | if fs == nil { 389 | continue 390 | } 391 | if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { 392 | return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) 393 | } 394 | } 395 | return nil 396 | } 397 | 398 | var ( 399 | errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") 400 | ) 401 | 402 | // ScanSlice scans src to the slice pointed to by dest. The elements the dest 403 | // slice must be integer, float, boolean, string, struct or pointer to struct 404 | // values. 405 | // 406 | // Struct fields must be integer, float, boolean or string values. All struct 407 | // fields are used unless a subset is specified using fieldNames. 408 | func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { 409 | d := reflect.ValueOf(dest) 410 | if d.Kind() != reflect.Ptr || d.IsNil() { 411 | return errScanSliceValue 412 | } 413 | d = d.Elem() 414 | if d.Kind() != reflect.Slice { 415 | return errScanSliceValue 416 | } 417 | 418 | isPtr := false 419 | t := d.Type().Elem() 420 | if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { 421 | isPtr = true 422 | t = t.Elem() 423 | } 424 | 425 | if t.Kind() != reflect.Struct { 426 | ensureLen(d, len(src)) 427 | for i, s := range src { 428 | if s == nil { 429 | continue 430 | } 431 | if err := convertAssignValue(d.Index(i), s); err != nil { 432 | return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) 433 | } 434 | } 435 | return nil 436 | } 437 | 438 | ss := structSpecForType(t) 439 | fss := ss.l 440 | if len(fieldNames) > 0 { 441 | fss = make([]*fieldSpec, len(fieldNames)) 442 | for i, name := range fieldNames { 443 | fss[i] = ss.m[name] 444 | if fss[i] == nil { 445 | return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) 446 | } 447 | } 448 | } 449 | 450 | if len(fss) == 0 { 451 | return errors.New("redigo.ScanSlice: no struct fields") 452 | } 453 | 454 | n := len(src) / len(fss) 455 | if n*len(fss) != len(src) { 456 | return errors.New("redigo.ScanSlice: length not a multiple of struct field count") 457 | } 458 | 459 | ensureLen(d, n) 460 | for i := 0; i < n; i++ { 461 | d := d.Index(i) 462 | if isPtr { 463 | if d.IsNil() { 464 | d.Set(reflect.New(t)) 465 | } 466 | d = d.Elem() 467 | } 468 | for j, fs := range fss { 469 | s := src[i*len(fss)+j] 470 | if s == nil { 471 | continue 472 | } 473 | if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { 474 | return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) 475 | } 476 | } 477 | } 478 | return nil 479 | } 480 | 481 | // Args is a helper for constructing command arguments from structured values. 482 | type Args []interface{} 483 | 484 | // Add returns the result of appending value to args. 485 | func (args Args) Add(value ...interface{}) Args { 486 | return append(args, value...) 487 | } 488 | 489 | // AddFlat returns the result of appending the flattened value of v to args. 490 | // 491 | // Maps are flattened by appending the alternating keys and map values to args. 492 | // 493 | // Slices are flattened by appending the slice elements to args. 494 | // 495 | // Structs are flattened by appending the alternating names and values of 496 | // exported fields to args. If v is a nil struct pointer, then nothing is 497 | // appended. The 'redis' field tag overrides struct field names. See ScanStruct 498 | // for more information on the use of the 'redis' field tag. 499 | // 500 | // Other types are appended to args as is. 501 | func (args Args) AddFlat(v interface{}) Args { 502 | rv := reflect.ValueOf(v) 503 | switch rv.Kind() { 504 | case reflect.Struct: 505 | args = flattenStruct(args, rv) 506 | case reflect.Slice: 507 | for i := 0; i < rv.Len(); i++ { 508 | args = append(args, rv.Index(i).Interface()) 509 | } 510 | case reflect.Map: 511 | for _, k := range rv.MapKeys() { 512 | args = append(args, k.Interface(), rv.MapIndex(k).Interface()) 513 | } 514 | case reflect.Ptr: 515 | if rv.Type().Elem().Kind() == reflect.Struct { 516 | if !rv.IsNil() { 517 | args = flattenStruct(args, rv.Elem()) 518 | } 519 | } else { 520 | args = append(args, v) 521 | } 522 | default: 523 | args = append(args, v) 524 | } 525 | return args 526 | } 527 | 528 | func flattenStruct(args Args, v reflect.Value) Args { 529 | ss := structSpecForType(v.Type()) 530 | for _, fs := range ss.l { 531 | fv := v.FieldByIndex(fs.index) 532 | if fs.omitEmpty { 533 | var empty = false 534 | switch fv.Kind() { 535 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 536 | empty = fv.Len() == 0 537 | case reflect.Bool: 538 | empty = !fv.Bool() 539 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 540 | empty = fv.Int() == 0 541 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 542 | empty = fv.Uint() == 0 543 | case reflect.Float32, reflect.Float64: 544 | empty = fv.Float() == 0 545 | case reflect.Interface, reflect.Ptr: 546 | empty = fv.IsNil() 547 | } 548 | if empty { 549 | continue 550 | } 551 | } 552 | args = append(args, fs.name, fv.Interface()) 553 | } 554 | return args 555 | } 556 | -------------------------------------------------------------------------------- /internal/redigo/redis/script.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "crypto/sha1" 19 | "encoding/hex" 20 | "io" 21 | "strings" 22 | ) 23 | 24 | // Script encapsulates the source, hash and key count for a Lua script. See 25 | // http://redis.io/commands/eval for information on scripts in Redis. 26 | type Script struct { 27 | keyCount int 28 | src string 29 | hash string 30 | } 31 | 32 | // NewScript returns a new script object. If keyCount is greater than or equal 33 | // to zero, then the count is automatically inserted in the EVAL command 34 | // argument list. If keyCount is less than zero, then the application supplies 35 | // the count as the first value in the keysAndArgs argument to the Do, Send and 36 | // SendHash methods. 37 | func NewScript(keyCount int, src string) *Script { 38 | h := sha1.New() 39 | io.WriteString(h, src) 40 | return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} 41 | } 42 | 43 | func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { 44 | var args []interface{} 45 | if s.keyCount < 0 { 46 | args = make([]interface{}, 1+len(keysAndArgs)) 47 | args[0] = spec 48 | copy(args[1:], keysAndArgs) 49 | } else { 50 | args = make([]interface{}, 2+len(keysAndArgs)) 51 | args[0] = spec 52 | args[1] = s.keyCount 53 | copy(args[2:], keysAndArgs) 54 | } 55 | return args 56 | } 57 | 58 | // Do evaluates the script. Under the covers, Do optimistically evaluates the 59 | // script using the EVALSHA command. If the command fails because the script is 60 | // not loaded, then Do evaluates the script using the EVAL command (thus 61 | // causing the script to load). 62 | func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { 63 | v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) 64 | if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { 65 | v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) 66 | } 67 | return v, err 68 | } 69 | 70 | // SendHash evaluates the script without waiting for the reply. The script is 71 | // evaluated with the EVALSHA command. The application must ensure that the 72 | // script is loaded by a previous call to Send, Do or Load methods. 73 | func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { 74 | return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) 75 | } 76 | 77 | // Send evaluates the script without waiting for the reply. 78 | func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { 79 | return c.Send("EVAL", s.args(s.src, keysAndArgs)...) 80 | } 81 | 82 | // Load loads the script without evaluating it. 83 | func (s *Script) Load(c Conn) error { 84 | _, err := c.Do("SCRIPT", "LOAD", s.src) 85 | return err 86 | } 87 | -------------------------------------------------------------------------------- /internal/redisutil.go: -------------------------------------------------------------------------------- 1 | // redisclient 2 | package internal 3 | 4 | import ( 5 | "encoding/json" 6 | "github.com/garyburd/redigo/redis" 7 | "sync" 8 | ) 9 | 10 | type RedisClient struct { 11 | pool *redis.Pool 12 | Address string 13 | } 14 | 15 | var OnConnError func() 16 | 17 | var ( 18 | redisMap map[string]*RedisClient 19 | mapMutex *sync.RWMutex 20 | ) 21 | 22 | const ( 23 | defaultTimeout = 60 * 10 //默认10分钟 24 | ) 25 | 26 | func init() { 27 | redisMap = make(map[string]*RedisClient) 28 | mapMutex = new(sync.RWMutex) 29 | } 30 | 31 | // 重写生成连接池方法 32 | // redisURL: connection string, like "redis://:password@10.0.1.11:6379/0" 33 | func newPool(redisURL string, maxIdle, maxActive int) *redis.Pool { 34 | return &redis.Pool{ 35 | MaxIdle: maxIdle, 36 | MaxActive: maxActive, // max number of connections 37 | Dial: func() (redis.Conn, error) { 38 | c, err := redis.DialURL(redisURL) 39 | return c, err 40 | }, 41 | } 42 | } 43 | 44 | //获取指定Address及连接池设置的RedisClient 45 | func GetRedisClient(address string, maxIdle int, maxActive int) *RedisClient { 46 | var redis *RedisClient 47 | var mok bool 48 | mapMutex.RLock() 49 | redis, mok = redisMap[address] 50 | mapMutex.RUnlock() 51 | if !mok { 52 | redis = &RedisClient{Address: address, pool: newPool(address, maxIdle, maxActive)} 53 | mapMutex.Lock() 54 | redisMap[address] = redis 55 | mapMutex.Unlock() 56 | } 57 | return redis 58 | } 59 | 60 | //获取指定key的内容, interface{} 61 | func (rc *RedisClient) GetObj(key string) (interface{}, error) { 62 | // 从连接池里面获得一个连接 63 | conn := rc.pool.Get() 64 | // 连接完关闭,其实没有关闭,是放回池里,也就是队列里面,等待下一个重用 65 | defer conn.Close() 66 | reply, errDo := innerDo(conn, "GET", key) 67 | return reply, errDo 68 | } 69 | 70 | //获取指定key的内容, string 71 | func (rc *RedisClient) Get(key string) (string, error) { 72 | val, err := redis.String(rc.GetObj(key)) 73 | return val, err 74 | } 75 | 76 | //检查指定key是否存在 77 | func (rc *RedisClient) Exists(key string) (bool, error) { 78 | // 从连接池里面获得一个连接 79 | conn := rc.pool.Get() 80 | // 连接完关闭,其实没有关闭,是放回池里,也就是队列里面,等待下一个重用 81 | defer conn.Close() 82 | reply, errDo := innerDo(conn, "EXISTS", key) 83 | if errDo == nil && reply == nil { 84 | return false, nil 85 | } 86 | val, err := redis.Int(reply, errDo) 87 | return val > 0, err 88 | } 89 | 90 | //删除指定key 91 | func (rc *RedisClient) Del(key ...interface{}) (int, error) { 92 | // 从连接池里面获得一个连接 93 | conn := rc.pool.Get() 94 | // 连接完关闭,其实没有关闭,是放回池里,也就是队列里面,等待下一个重用 95 | defer conn.Close() 96 | reply, errDo := conn.Do("DEL", key...) 97 | if errDo == nil && reply == nil { 98 | return 0, nil 99 | } 100 | val, err := redis.Int(reply, errDo) 101 | return val, err 102 | } 103 | 104 | //对存储在指定key的数值执行原子的加1操作 105 | func (rc *RedisClient) INCR(key string) (int, error) { 106 | conn := rc.pool.Get() 107 | defer conn.Close() 108 | reply, errDo := innerDo(conn, "INCR", key) 109 | if errDo == nil && reply == nil { 110 | return 0, nil 111 | } 112 | val, err := redis.Int(reply, errDo) 113 | return val, err 114 | } 115 | 116 | //对存储在指定key的数值执行原子的减1操作 117 | func (rc *RedisClient) DECR(key string) (int, error) { 118 | conn := rc.pool.Get() 119 | defer conn.Close() 120 | reply, errDo := innerDo(conn, "DECR", key) 121 | if errDo == nil && reply == nil { 122 | return 0, nil 123 | } 124 | val, err := redis.Int(reply, errDo) 125 | return val, err 126 | } 127 | 128 | func (rc *RedisClient) Set(key string, val interface{}) (interface{}, error) { 129 | conn := rc.pool.Get() 130 | defer conn.Close() 131 | val, err := innerDo(conn, "SET", key, val) 132 | return val, err 133 | } 134 | 135 | // SetWithExpire 设置指定key的内容 136 | func (rc *RedisClient) SetWithExpire(key string, val interface{}, timeOutSeconds int64) (interface{}, error) { 137 | conn := rc.pool.Get() 138 | defer conn.Close() 139 | val, err := innerDo(conn, "SET", key, val, "EX", timeOutSeconds) 140 | return val, err 141 | } 142 | 143 | // SetNX 将 key 的值设为 value ,当且仅当 key 不存在。 144 | // 若给定的 key 已经存在,则 SETNX 不做任何动作。 成功返回1, 失败返回0 145 | func (rc *RedisClient) SetNX(key, value string) (int, error) { 146 | conn := rc.pool.Get() 147 | defer conn.Close() 148 | 149 | val, err := redis.Int(innerDo(conn, "SETNX", key, value)) 150 | return val, err 151 | } 152 | 153 | // Expire 设置指定key的过期时间 154 | func (rc *RedisClient) Expire(key string, timeOutSeconds int) (int, error) { 155 | conn := rc.pool.Get() 156 | defer conn.Close() 157 | val, err := redis.Int(innerDo(conn, "EXPIRE", key, timeOutSeconds)) 158 | return val, err 159 | } 160 | 161 | // GetJsonObj get obj with SetJsonObj key 162 | func (rc *RedisClient) GetJsonObj(key string, result interface{}) error { 163 | jsonStr, err := redis.String(rc.GetObj(key)) 164 | if err != nil { 165 | return err 166 | } 167 | err = json.Unmarshal([]byte(jsonStr), result) 168 | return err 169 | } 170 | 171 | // SetJsonObj set obj use json encode string 172 | func (rc *RedisClient) SetJsonObj(key string, val interface{}) (interface{}, error) { 173 | conn := rc.pool.Get() 174 | defer conn.Close() 175 | jsonStr, err := json.Marshal(val) 176 | if err != nil { 177 | return nil, err 178 | } 179 | reply, err := redis.String(innerDo(conn, "SET", key, jsonStr)) 180 | return reply, err 181 | } 182 | 183 | //删除当前数据库里面的所有数据 184 | //这个命令永远不会出现失败 185 | func (rc *RedisClient) FlushDB() { 186 | conn := rc.pool.Get() 187 | defer conn.Close() 188 | conn.Do("FLUSHALL") 189 | } 190 | 191 | //****************** hash 哈希表 *********************** 192 | 193 | //获取指定hashset的所有内容 194 | func (rc *RedisClient) HGetAll(hashID string) (map[string]string, error) { 195 | conn := rc.pool.Get() 196 | defer conn.Close() 197 | reply, err := redis.StringMap(innerDo(conn, "HGETALL", hashID)) 198 | return reply, err 199 | } 200 | 201 | //获取指定hashset的内容 202 | func (rc *RedisClient) HGet(hashID string, field string) (string, error) { 203 | conn := rc.pool.Get() 204 | defer conn.Close() 205 | reply, errDo := conn.Do("HGET", hashID, field) 206 | if errDo == nil && reply == nil { 207 | return "", nil 208 | } 209 | val, err := redis.String(reply, errDo) 210 | return val, err 211 | } 212 | 213 | // HMGet 返回 key 指定的哈希集中指定字段的值 214 | func (rc *RedisClient) HMGet(hashID string, field ...interface{}) ([]string, error) { 215 | conn := rc.pool.Get() 216 | defer conn.Close() 217 | args := append([]interface{}{hashID}, field...) 218 | reply, err := redis.Strings(innerDo(conn, "HMGET", args...)) 219 | return reply, err 220 | } 221 | 222 | //设置指定hashset的内容 223 | func (rc *RedisClient) HSet(hashID string, field string, val string) error { 224 | conn := rc.pool.Get() 225 | defer conn.Close() 226 | _, err := innerDo(conn, "HSET", hashID, field, val) 227 | return err 228 | } 229 | 230 | func (rc *RedisClient) HSetNX(hashID string, field string, val string) (string, error) { 231 | conn := rc.pool.Get() 232 | defer conn.Close() 233 | reply, err := redis.String(innerDo(conn, "HSETNX", hashID, field, val)) 234 | return reply, err 235 | } 236 | 237 | func (rc *RedisClient) HDel(key string, field ...interface{}) (int, error) { 238 | conn := rc.pool.Get() 239 | defer conn.Close() 240 | args := append([]interface{}{key}, field...) 241 | val, err := redis.Int(innerDo(conn, "HDEL", args...)) 242 | return val, err 243 | } 244 | 245 | func (rc *RedisClient) HExist(key string, field string) (int, error) { 246 | conn := rc.pool.Get() 247 | defer conn.Close() 248 | val, err := redis.Int(innerDo(conn, "HEXISTS", key, field)) 249 | return val, err 250 | } 251 | func (rc *RedisClient) HIncrBy(key string, field string, increment int) (int, error) { 252 | conn := rc.pool.Get() 253 | defer conn.Close() 254 | val, err := redis.Int(innerDo(conn, "HINCRBY", key, field, increment)) 255 | return val, err 256 | } 257 | 258 | func (rc *RedisClient) HIncrByFloat(key string, field string, increment float64) (float64, error) { 259 | conn := rc.pool.Get() 260 | defer conn.Close() 261 | val, err := redis.Float64(innerDo(conn, "HINCRBYFLOAT", key, field, increment)) 262 | return val, err 263 | } 264 | 265 | func (rc *RedisClient) HKeys(key string) ([]string, error) { 266 | conn := rc.pool.Get() 267 | defer conn.Close() 268 | val, err := redis.Strings(innerDo(conn, "HKEYS", key)) 269 | return val, err 270 | } 271 | 272 | // HLen 返回哈希表 key 中域的数量, 当 key 不存在时,返回0 273 | func (rc *RedisClient) HLen(key string) (int, error) { 274 | conn := rc.pool.Get() 275 | defer conn.Close() 276 | val, err := redis.Int(innerDo(conn, "HLEN", key)) 277 | return val, err 278 | } 279 | 280 | // HVals 返回哈希表 key 中所有域的值, 当 key 不存在时,返回空 281 | func (rc *RedisClient) HVals(key string) ([]string, error) { 282 | conn := rc.pool.Get() 283 | defer conn.Close() 284 | val, err := redis.Strings(innerDo(conn, "HVALS", key)) 285 | return val, err 286 | } 287 | 288 | //****************** list 链表 *********************** 289 | 290 | //将所有指定的值插入到存于 key 的列表的头部 291 | func (rc *RedisClient) LPush(key string, value ...interface{}) (int, error) { 292 | conn := rc.pool.Get() 293 | defer conn.Close() 294 | ret, err := redis.Int(innerDo(conn, "LPUSH", key, value)) 295 | if err != nil { 296 | return -1, err 297 | } else { 298 | return ret, nil 299 | } 300 | } 301 | 302 | func (rc *RedisClient) LPushX(key string, value string) (int, error) { 303 | conn := rc.pool.Get() 304 | defer conn.Close() 305 | resp, err := redis.Int(innerDo(conn, "LPUSHX", key, value)) 306 | return resp, err 307 | } 308 | 309 | func (rc *RedisClient) LRange(key string, start int, stop int) ([]string, error) { 310 | conn := rc.pool.Get() 311 | defer conn.Close() 312 | resp, err := redis.Strings(innerDo(conn, "LRANGE", key, start, stop)) 313 | return resp, err 314 | } 315 | 316 | func (rc *RedisClient) LRem(key string, count int, value string) (int, error) { 317 | conn := rc.pool.Get() 318 | defer conn.Close() 319 | resp, err := redis.Int(innerDo(conn, "LREM", key, count, value)) 320 | return resp, err 321 | } 322 | 323 | func (rc *RedisClient) LSet(key string, index int, value string) (string, error) { 324 | conn := rc.pool.Get() 325 | defer conn.Close() 326 | resp, err := redis.String(innerDo(conn, "LSET", key, index, value)) 327 | return resp, err 328 | } 329 | 330 | func (rc *RedisClient) LTrim(key string, start int, stop int) (string, error) { 331 | conn := rc.pool.Get() 332 | defer conn.Close() 333 | resp, err := redis.String(innerDo(conn, "LTRIM", key, start, stop)) 334 | return resp, err 335 | } 336 | 337 | func (rc *RedisClient) RPop(key string) (string, error) { 338 | conn := rc.pool.Get() 339 | defer conn.Close() 340 | resp, err := redis.String(innerDo(conn, "RPOP", key)) 341 | return resp, err 342 | } 343 | 344 | func (rc *RedisClient) RPush(key string, value ...interface{}) (int, error) { 345 | conn := rc.pool.Get() 346 | defer conn.Close() 347 | args := append([]interface{}{key}, value...) 348 | resp, err := redis.Int(innerDo(conn, "RPUSH", args...)) 349 | return resp, err 350 | } 351 | 352 | func (rc *RedisClient) RPushX(key string, value ...interface{}) (int, error) { 353 | conn := rc.pool.Get() 354 | defer conn.Close() 355 | args := append([]interface{}{key}, value...) 356 | resp, err := redis.Int(innerDo(conn, "RPUSHX", args...)) 357 | return resp, err 358 | } 359 | 360 | func (rc *RedisClient) RPopLPush(source string, destination string) (string, error) { 361 | conn := rc.pool.Get() 362 | defer conn.Close() 363 | resp, err := redis.String(innerDo(conn, "RPOPLPUSH", source, destination)) 364 | return resp, err 365 | } 366 | 367 | func (rc *RedisClient) BLPop(key ...interface{}) (map[string]string, error) { 368 | conn := rc.pool.Get() 369 | defer conn.Close() 370 | val, err := redis.StringMap(innerDo(conn, "BLPOP", key, defaultTimeout)) 371 | return val, err 372 | } 373 | 374 | //删除,并获得该列表中的最后一个元素,或阻塞,直到有一个可用 375 | func (rc *RedisClient) BRPop(key ...interface{}) (map[string]string, error) { 376 | conn := rc.pool.Get() 377 | defer conn.Close() 378 | val, err := redis.StringMap(innerDo(conn, "BRPOP", key, defaultTimeout)) 379 | return val, err 380 | } 381 | 382 | func (rc *RedisClient) BRPopLPush(source string, destination string) (string, error) { 383 | conn := rc.pool.Get() 384 | defer conn.Close() 385 | val, err := redis.String(innerDo(conn, "BRPOPLPUSH", source, destination)) 386 | return val, err 387 | } 388 | 389 | func (rc *RedisClient) LIndex(key string, index int) (string, error) { 390 | conn := rc.pool.Get() 391 | defer conn.Close() 392 | val, err := redis.String(innerDo(conn, "LINDEX", key, index)) 393 | return val, err 394 | } 395 | 396 | func (rc *RedisClient) LInsertBefore(key string, pivot string, value string) (int, error) { 397 | conn := rc.pool.Get() 398 | defer conn.Close() 399 | val, err := redis.Int(innerDo(conn, "LINSERT", key, "BEFORE", pivot, value)) 400 | return val, err 401 | } 402 | 403 | func (rc *RedisClient) LInsertAfter(key string, pivot string, value string) (int, error) { 404 | conn := rc.pool.Get() 405 | defer conn.Close() 406 | val, err := redis.Int(innerDo(conn, "LINSERT", key, "AFTER", pivot, value)) 407 | return val, err 408 | } 409 | 410 | func (rc *RedisClient) LLen(key string) (int, error) { 411 | conn := rc.pool.Get() 412 | defer conn.Close() 413 | val, err := redis.Int(innerDo(conn, "LLEN", key)) 414 | return val, err 415 | } 416 | 417 | func (rc *RedisClient) LPop(key string) (string, error) { 418 | conn := rc.pool.Get() 419 | defer conn.Close() 420 | val, err := redis.String(innerDo(conn, "LPOP", key)) 421 | return val, err 422 | } 423 | 424 | //****************** set 集合 *********************** 425 | 426 | // SAdd 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。 427 | // 假如 key 不存在,则创建一个只包含 member 元素作成员的集合。 428 | func (rc *RedisClient) SAdd(key string, member ...interface{}) (int, error) { 429 | conn := rc.pool.Get() 430 | defer conn.Close() 431 | args := append([]interface{}{key}, member...) 432 | val, err := redis.Int(innerDo(conn, "SADD", args...)) 433 | return val, err 434 | } 435 | 436 | // SCard 返回集合 key 的基数(集合中元素的数量)。 437 | // 返回值: 438 | // 集合的基数。 439 | // 当 key 不存在时,返回 0 440 | func (rc *RedisClient) SCard(key string) (int, error) { 441 | conn := rc.pool.Get() 442 | defer conn.Close() 443 | val, err := redis.Int(innerDo(conn, "SCARD", key)) 444 | return val, err 445 | } 446 | 447 | // SPop 移除并返回集合中的一个随机元素。 448 | // 如果只想获取一个随机元素,但不想该元素从集合中被移除的话,可以使用 SRANDMEMBER 命令。 449 | // count 为 返回的随机元素的数量 450 | func (rc *RedisClient) SPop(key string) (string, error) { 451 | conn := rc.pool.Get() 452 | defer conn.Close() 453 | val, err := redis.String(innerDo(conn, "SPOP", key)) 454 | return val, err 455 | } 456 | 457 | // SRandMember 如果命令执行时,只提供了 key 参数,那么返回集合中的一个随机元素。 458 | // 该操作和 SPOP 相似,但 SPOP 将随机元素从集合中移除并返回,而 SRANDMEMBER 则仅仅返回随机元素,而不对集合进行任何改动。 459 | // count 为 返回的随机元素的数量 460 | func (rc *RedisClient) SRandMember(key string, count int) ([]string, error) { 461 | conn := rc.pool.Get() 462 | defer conn.Close() 463 | val, err := redis.Strings(innerDo(conn, "SRANDMEMBER", key, count)) 464 | return val, err 465 | } 466 | 467 | // SRem 移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。 468 | // 当 key 不是集合类型,返回一个错误。 469 | // 在 Redis 2.4 版本以前, SREM 只接受单个 member 值。 470 | func (rc *RedisClient) SRem(key string, member ...interface{}) (int, error) { 471 | conn := rc.pool.Get() 472 | defer conn.Close() 473 | args := append([]interface{}{key}, member...) 474 | val, err := redis.Int(innerDo(conn, "SREM", args...)) 475 | return val, err 476 | } 477 | 478 | func (rc *RedisClient) SDiff(key ...interface{}) ([]string, error) { 479 | conn := rc.pool.Get() 480 | defer conn.Close() 481 | val, err := redis.Strings(innerDo(conn, "SDIFF", key...)) 482 | return val, err 483 | } 484 | 485 | func (rc *RedisClient) SDiffStore(destination string, key ...interface{}) (int, error) { 486 | conn := rc.pool.Get() 487 | defer conn.Close() 488 | args := append([]interface{}{destination}, key...) 489 | val, err := redis.Int(innerDo(conn, "SDIFFSTORE", args...)) 490 | return val, err 491 | } 492 | 493 | func (rc *RedisClient) SInter(key ...interface{}) ([]string, error) { 494 | conn := rc.pool.Get() 495 | defer conn.Close() 496 | val, err := redis.Strings(innerDo(conn, "SINTER", key...)) 497 | return val, err 498 | } 499 | 500 | func (rc *RedisClient) SInterStore(destination string, key ...interface{}) (int, error) { 501 | conn := rc.pool.Get() 502 | defer conn.Close() 503 | args := append([]interface{}{destination}, key...) 504 | val, err := redis.Int(innerDo(conn, "SINTERSTORE", args...)) 505 | return val, err 506 | } 507 | 508 | func (rc *RedisClient) SIsMember(key string, member string) (bool, error) { 509 | conn := rc.pool.Get() 510 | defer conn.Close() 511 | val, err := redis.Bool(innerDo(conn, "SISMEMBER", key, member)) 512 | return val, err 513 | } 514 | 515 | func (rc *RedisClient) SMembers(key string) ([]string, error) { 516 | conn := rc.pool.Get() 517 | defer conn.Close() 518 | val, err := redis.Strings(innerDo(conn, "SMEMBERS", key)) 519 | return val, err 520 | } 521 | 522 | // smove is a atomic operate 523 | func (rc *RedisClient) SMove(source string, destination string, member string) (bool, error) { 524 | conn := rc.pool.Get() 525 | defer conn.Close() 526 | val, err := redis.Bool(innerDo(conn, "SMOVE", source, destination, member)) 527 | return val, err 528 | } 529 | 530 | func (rc *RedisClient) SUnion(key ...interface{}) ([]string, error) { 531 | conn := rc.pool.Get() 532 | defer conn.Close() 533 | val, err := redis.Strings(innerDo(conn, "SUNION", key...)) 534 | return val, err 535 | } 536 | 537 | func (rc *RedisClient) SUnionStore(destination string, key ...interface{}) (int, error) { 538 | conn := rc.pool.Get() 539 | defer conn.Close() 540 | args := append([]interface{}{destination}, key...) 541 | val, err := redis.Int(innerDo(conn, "SUNIONSTORE", args)) 542 | return val, err 543 | } 544 | 545 | //****************** sorted set 集合 *********************** 546 | 547 | // ZAdd 将所有指定成员添加到键为key有序集合(sorted set)里面。 添加时可以指定多个分数/成员(score/member)对。 548 | // 如果指定添加的成员已经是有序集合里面的成员,则会更新改成员的分数(scrore)并更新到正确的排序位置 549 | func (rc *RedisClient) ZAdd(key string, score int64, member interface{}) (int, error) { 550 | conn := rc.pool.Get() 551 | defer conn.Close() 552 | args := append([]interface{}{key}, score, member) 553 | val, err := redis.Int(innerDo(conn, "ZADD", args...)) 554 | return val, err 555 | } 556 | 557 | // ZCount 返回有序集key中,score值在min和max之间(默认包括score值等于min或max)的成员 558 | func (rc *RedisClient) ZCount(key string, min, max int64) (int, error) { 559 | conn := rc.pool.Get() 560 | defer conn.Close() 561 | args := append([]interface{}{key}, min, max) 562 | val, err := redis.Int(innerDo(conn, "ZCOUNT", args...)) 563 | return val, err 564 | } 565 | 566 | // ZRem 从排序的集合中删除一个或多个成员 567 | // 当key存在,但是其不是有序集合类型,就返回一个错误。 568 | func (rc *RedisClient) ZRem(key string, member ...interface{}) (int, error) { 569 | conn := rc.pool.Get() 570 | defer conn.Close() 571 | args := append([]interface{}{key}, member...) 572 | val, err := redis.Int(innerDo(conn, "ZREM", args...)) 573 | return val, err 574 | } 575 | 576 | // ZCard 返回key的有序集元素个数 577 | func (rc *RedisClient) ZCard(key string) (int, error) { 578 | conn := rc.pool.Get() 579 | defer conn.Close() 580 | args := append([]interface{}{key}) 581 | val, err := redis.Int(innerDo(conn, "ZCARD", args...)) 582 | return val, err 583 | } 584 | 585 | // ZRank 返回有序集key中成员member的排名 586 | func (rc *RedisClient) ZRank(key, member string) (int, error) { 587 | conn := rc.pool.Get() 588 | defer conn.Close() 589 | args := append([]interface{}{key}, member) 590 | val, err := redis.Int(innerDo(conn, "ZRANK", args...)) 591 | return val, err 592 | } 593 | 594 | // ZRange Returns the specified range of elements in the sorted set stored at key 595 | func (rc *RedisClient) ZRange(key string, start, stop int64) ([]string, error) { 596 | conn := rc.pool.Get() 597 | defer conn.Close() 598 | args := append([]interface{}{key}, start, stop) 599 | val, err := redis.Strings(innerDo(conn, "ZRANGE", args...)) 600 | return val, err 601 | } 602 | 603 | // ZRangeByScore Returns all the elements in the sorted set at key with a score between min and max (including elements with score equal to min or max). 604 | func (rc *RedisClient) ZRangeByScore(key string, start, stop string, isWithScores bool) ([]string, error) { 605 | conn := rc.pool.Get() 606 | defer conn.Close() 607 | args := append([]interface{}{key}, start, stop) 608 | if isWithScores { 609 | args = append(args, "WITHSCORES") 610 | } 611 | val, err := redis.Strings(innerDo(conn, "ZRANGEBYSCORE", args...)) 612 | return val, err 613 | } 614 | 615 | // ZREVRangeByScore Returns all the elements in the sorted set at key with a score between max and min (including elements with score equal to max or min). In contrary to the default ordering of sorted sets, for this command the elements are considered to be ordered from high to low scores. 616 | func (rc *RedisClient) ZREVRangeByScore(key string, max, min string, isWithScores bool) ([]string, error) { 617 | conn := rc.pool.Get() 618 | defer conn.Close() 619 | args := append([]interface{}{key}, max, min) 620 | if isWithScores { 621 | args = append(args, "WITHSCORES") 622 | } 623 | val, err := redis.Strings(innerDo(conn, "ZREVRANGEBYSCORE", args...)) 624 | return val, err 625 | } 626 | 627 | // ZRange Returns the specified range of elements in the sorted set stored at key 628 | func (rc *RedisClient) ZRevRange(key string, start, stop int64) ([]string, error) { 629 | conn := rc.pool.Get() 630 | defer conn.Close() 631 | args := append([]interface{}{key}, start, stop) 632 | val, err := redis.Strings(innerDo(conn, "ZREVRANGE", args...)) 633 | return val, err 634 | } 635 | 636 | //****************** PUB/SUB ********************* 637 | 638 | // Publish 将信息 message 发送到指定的频道 channel 639 | func (rc *RedisClient) Publish(channel string, message interface{}) (int64, error) { 640 | conn := rc.pool.Get() 641 | defer conn.Close() 642 | var args []interface{} 643 | args = append([]interface{}{channel}, message) 644 | val, err := redis.Int64(innerDo(conn, "PUBLISH", args...)) 645 | return val, err 646 | } 647 | 648 | //****************** lua scripts ********************* 649 | // EVAL 使用内置的 Lua 解释器 650 | func (rc *RedisClient) EVAL(script string, argsNum int, arg ...interface{}) (interface{}, error) { 651 | conn := rc.pool.Get() 652 | defer conn.Close() 653 | var args []interface{} 654 | if len(arg) > 0 { 655 | args = append([]interface{}{script, argsNum}, arg...) 656 | } else { 657 | args = append([]interface{}{script, argsNum}) 658 | } 659 | args = append([]interface{}{script, argsNum}, arg...) 660 | val, err := innerDo(conn, "EVAL", args...) 661 | return val, err 662 | } 663 | 664 | //****************** 全局操作 *********************** 665 | // DBSize 返回当前数据库的 key 的数量 666 | func (rc *RedisClient) DBSize() (int, error) { 667 | conn := rc.pool.Get() 668 | defer conn.Close() 669 | val, err := redis.Int(innerDo(conn, "DBSIZE")) 670 | return val, err 671 | } 672 | 673 | // Ping ping command, if success return pong 674 | func (rc *RedisClient) Ping() (string, error) { 675 | conn := rc.pool.Get() 676 | defer conn.Close() 677 | var args []interface{} 678 | val, err := redis.String(innerDo(conn, "PING", args...)) 679 | return val, err 680 | } 681 | 682 | // GetConn 返回一个从连接池获取的redis连接, 683 | // 需要手动释放redis连接 684 | func (rc *RedisClient) GetConn() redis.Conn { 685 | return rc.pool.Get() 686 | } 687 | 688 | // Do sends a command to the server and returns the received reply. 689 | func innerDo(conn redis.Conn, commandName string, args ...interface{}) (interface{}, error) { 690 | reply, err := conn.Do(commandName, args...) 691 | return reply, err 692 | } 693 | -------------------------------------------------------------------------------- /internal/redisutil_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | var rc *RedisClient 10 | func init() { 11 | rc = GetRedisClient("redis://192.168.8.175:6379/0", 10, 10) 12 | } 13 | 14 | func TestRedisClient_Ping(t *testing.T) { 15 | res, err :=rc.Ping() 16 | if err != nil { 17 | fmt.Println(err.Error()) 18 | } 19 | fmt.Println(res, reflect.TypeOf(res)) 20 | } 21 | 22 | func TestRedisClient_Del(t *testing.T) { 23 | res, err :=rc.Del("1", "", "f12") 24 | if err != nil { 25 | fmt.Println(err.Error()) 26 | } 27 | fmt.Println(res, reflect.TypeOf(res)) 28 | } 29 | 30 | func TestRedisClient_HDel(t *testing.T) { 31 | res, err :=rc.HDel("h1", "f11", "f12") 32 | if err != nil { 33 | fmt.Println(err.Error()) 34 | } 35 | fmt.Println(res, reflect.TypeOf(res)) 36 | } 37 | 38 | func TestRedisClient_HSetNX(t *testing.T) { 39 | res, err :=rc.HSetNX("h1", "f11", "f12") 40 | if err != nil { 41 | fmt.Println(err.Error()) 42 | } 43 | fmt.Println(res, reflect.TypeOf(res)) 44 | } 45 | 46 | func TestRedisClient_HVals(t *testing.T) { 47 | res, err :=rc.HVals("h1") 48 | if err != nil { 49 | fmt.Println(err.Error()) 50 | } 51 | fmt.Println(res, reflect.TypeOf(res)) 52 | } 53 | 54 | func TestRedisClient_SDiff(t *testing.T) { 55 | res, err :=rc.SDiff("skey1", "skey2") 56 | if err != nil { 57 | fmt.Println(err.Error()) 58 | } 59 | fmt.Println(res, reflect.TypeOf(res)) 60 | } -------------------------------------------------------------------------------- /redis/cache_redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/devfeel/cache/internal" //internal目录 不允许其他包调用, commit时候改回来 7 | "github.com/devfeel/cache/internal/hystrix" 8 | "github.com/garyburd/redigo/redis" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | var ( 14 | ZeroInt64 int64 = 0 15 | ) 16 | 17 | const ( 18 | LInsert_Before = "BEFORE" 19 | LInsert_After = "AFTER" 20 | HystrixErrorCount = 50 21 | ) 22 | 23 | // Message represents a message notification. 24 | type Message struct { 25 | // The originating channel. 26 | Channel string 27 | 28 | // The message data. 29 | Data []byte 30 | } 31 | 32 | // RedisCache is redis cache adapter. 33 | // it contains serverIp for redis conn. 34 | type redisCache struct { 35 | hystrix hystrix.Hystrix 36 | 37 | serverUrl string //connection string, like "redis://:password@10.0.1.11:6379/0" 38 | // Maximum number of idle connections in the pool. 39 | maxIdle int 40 | // Maximum number of connections allocated by the pool at a given time. 41 | // When zero, there is no limit on the number of connections in the pool. 42 | maxActive int 43 | 44 | //use to readonly server 45 | readOnlyServerUrl string 46 | readOnlyMaxIdle int 47 | readOnlyMaxActive int 48 | 49 | //use to backup server 50 | backupServerUrl string 51 | backupMaxIdle int 52 | backupMaxActive int 53 | } 54 | 55 | // NewRedisCache returns a new *RedisCache. 56 | func NewRedisCache(serverUrl string, maxIdle int, maxActive int) *redisCache { 57 | cache := redisCache{serverUrl: serverUrl, maxIdle: maxIdle, maxActive: maxActive} 58 | cache.hystrix = hystrix.NewHystrix(cache.checkRedisAlive, nil) 59 | cache.hystrix.SetMaxFailedNumber(HystrixErrorCount) 60 | cache.hystrix.Do() 61 | return &cache 62 | } 63 | 64 | // SetReadOnlyServer set readonly redis server 65 | func (ca *redisCache) SetReadOnlyServer(serverUrl string, maxIdle int, maxActive int) { 66 | ca.readOnlyServerUrl = serverUrl 67 | ca.readOnlyMaxActive = maxActive 68 | ca.readOnlyMaxIdle = maxIdle 69 | } 70 | 71 | // SetBackupServer set backup redis server, only use to read 72 | func (ca *redisCache) SetBackupServer(serverUrl string, maxIdle int, maxActive int) { 73 | ca.backupServerUrl = serverUrl 74 | ca.backupMaxActive = maxActive 75 | ca.backupMaxIdle = maxIdle 76 | } 77 | 78 | // Exists check item exist in redis cache. 79 | func (ca *redisCache) Exists(key string) (bool, error) { 80 | client := ca.getReadRedisClient() 81 | exists, err := client.Exists(key) 82 | if ca.checkConnErrorAndNeedRetry(err) { 83 | client = ca.getBackupRedis() 84 | return client.Exists(key) 85 | } 86 | return exists, err 87 | } 88 | 89 | // Incr increase int64 counter in redis cache. 90 | func (ca *redisCache) Incr(key string) (int64, error) { 91 | client := ca.getDefaultRedis() 92 | val, err := client.INCR(key) 93 | if err != nil { 94 | return 0, err 95 | } 96 | return int64(val), nil 97 | } 98 | 99 | // Decr decrease counter in redis cache. 100 | func (ca *redisCache) Decr(key string) (int64, error) { 101 | client := ca.getDefaultRedis() 102 | val, err := client.DECR(key) 103 | if err != nil { 104 | return 0, err 105 | } 106 | return int64(val), nil 107 | } 108 | 109 | // Get cache from redis cache. 110 | // if non-existed or expired, return nil. 111 | func (ca *redisCache) Get(key string) (interface{}, error) { 112 | client := ca.getReadRedisClient() 113 | reply, err := client.GetObj(key) 114 | if ca.checkConnErrorAndNeedRetry(err) { 115 | client = ca.getBackupRedis() 116 | return client.GetObj(key) 117 | } 118 | return reply, err 119 | } 120 | 121 | // GetString returns value string format by given key 122 | // if non-existed or expired, return "". 123 | func (ca *redisCache) GetString(key string) (string, error) { 124 | client := ca.getReadRedisClient() 125 | reply, err := client.Get(key) 126 | if ca.checkConnErrorAndNeedRetry(err) { 127 | client = ca.getBackupRedis() 128 | return client.Get(key) 129 | } 130 | return reply, err 131 | } 132 | 133 | // GetInt returns value int format by given key 134 | // if non-existed or expired, return nil. 135 | func (ca *redisCache) GetInt(key string) (int, error) { 136 | v, err := ca.GetString(key) 137 | if err != nil || v == "" { 138 | return 0, err 139 | } else { 140 | i, e := strconv.Atoi(v) 141 | if e != nil { 142 | return 0, err 143 | } else { 144 | return i, nil 145 | } 146 | } 147 | } 148 | 149 | // GetInt64 returns value int64 format by given key 150 | // if non-existed or expired, return nil. 151 | func (ca *redisCache) GetInt64(key string) (int64, error) { 152 | v, err := ca.GetString(key) 153 | if err != nil || v == "" { 154 | return ZeroInt64, err 155 | } else { 156 | i, e := strconv.ParseInt(v, 10, 64) 157 | if e != nil { 158 | return ZeroInt64, err 159 | } else { 160 | return i, nil 161 | } 162 | } 163 | } 164 | 165 | // Set cache to redis. 166 | // ttl is second, if ttl is 0, it will be forever. 167 | func (ca *redisCache) Set(key string, value interface{}, ttl int64) error { 168 | var err error 169 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 170 | if ttl > 0 { 171 | _, err = client.SetWithExpire(key, value, ttl) 172 | } else { 173 | _, err = client.Set(key, value) 174 | } 175 | return err 176 | } 177 | 178 | // Delete item in redis cacha. 179 | // if not exists, we think it's success 180 | func (ca *redisCache) Delete(key string) error { 181 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 182 | _, err := client.Del(key) 183 | return err 184 | } 185 | 186 | // Expire Set a timeout on key. After the timeout has expired, the key will automatically be deleted. 187 | func (ca *redisCache) Expire(key string, timeOutSeconds int) (int, error) { 188 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 189 | reply, err := client.Expire(key, timeOutSeconds) 190 | return reply, err 191 | } 192 | 193 | // GetJsonObj get obj with SetJsonObj key 194 | func (ca *redisCache) GetJsonObj(key string, result interface{}) error { 195 | client := ca.getReadRedisClient() 196 | err := client.GetJsonObj(key, result) 197 | if ca.checkConnErrorAndNeedRetry(err) { 198 | client = ca.getBackupRedis() 199 | return client.GetJsonObj(key, result) 200 | } 201 | return err 202 | } 203 | 204 | // SetJsonObj set obj use json encode string 205 | func (ca *redisCache) SetJsonObj(key string, val interface{}) (interface{}, error) { 206 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 207 | reply, err := client.SetJsonObj(key, val) 208 | return reply, err 209 | } 210 | 211 | /*---------- Hash -----------*/ 212 | // HGet Returns the value associated with field in the hash stored at key. 213 | func (ca *redisCache) HGet(key, field string) (string, error) { 214 | client := ca.getReadRedisClient() 215 | reply, err := client.HGet(key, field) 216 | if ca.checkConnErrorAndNeedRetry(err) { 217 | client = ca.getBackupRedis() 218 | return client.HGet(key, field) 219 | } 220 | return reply, err 221 | } 222 | 223 | // HMGet Returns the values associated with the specified fields in the hash stored at key. 224 | func (ca *redisCache) HMGet(hashID string, field ...interface{}) ([]string, error) { 225 | client := ca.getReadRedisClient() 226 | reply, err := client.HMGet(hashID, field...) 227 | if ca.checkConnErrorAndNeedRetry(err) { 228 | client = ca.getBackupRedis() 229 | return client.HMGet(hashID, field...) 230 | } 231 | return reply, err 232 | } 233 | 234 | // HGetAll Returns all fields and values of the hash stored at key 235 | func (ca *redisCache) HGetAll(key string) (map[string]string, error) { 236 | client := ca.getReadRedisClient() 237 | reply, err := client.HGetAll(key) 238 | if ca.checkConnErrorAndNeedRetry(err) { 239 | client = ca.getBackupRedis() 240 | return client.HGetAll(key) 241 | } 242 | return reply, err 243 | } 244 | 245 | // HSet Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created. 246 | // If field already exists in the hash, it is overwritten. 247 | func (ca *redisCache) HSet(key, field, value string) error { 248 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 249 | err := client.HSet(key, field, value) 250 | return err 251 | } 252 | 253 | // HDel Removes the specified fields from the hash stored at key. 254 | func (ca *redisCache) HDel(key string, field ...interface{}) (int, error) { 255 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 256 | reply, err := client.HDel(key, field...) 257 | return reply, err 258 | } 259 | 260 | // HExists Returns if field is an existing field in the hash stored at key 261 | func (ca *redisCache) HExists(key string, field string) (int, error) { 262 | client := ca.getReadRedisClient() 263 | reply, err := client.HExist(key, field) 264 | if ca.checkConnErrorAndNeedRetry(err) { 265 | client = ca.getBackupRedis() 266 | return client.HExist(key, field) 267 | } 268 | return reply, err 269 | } 270 | 271 | // HSetNX Sets field in the hash stored at key to value, only if field does not yet exist 272 | func (ca *redisCache) HSetNX(key string, field string, value string) (string, error) { 273 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 274 | reply, err := client.HSetNX(key, field, value) 275 | return reply, err 276 | } 277 | 278 | // HIncrBy Increments the number stored at field in the hash stored at key by increment. 279 | func (ca *redisCache) HIncrBy(key string, field string, increment int) (int, error) { 280 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 281 | reply, err := client.HIncrBy(key, field, increment) 282 | return reply, err 283 | } 284 | 285 | // HIncrByFloat Increment the specified field of a hash stored at key, and representing a floating point number, by the specified increment 286 | func (ca *redisCache) HIncrByFloat(key string, field string, increment float64) (float64, error) { 287 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 288 | reply, err := client.HIncrByFloat(key, field, increment) 289 | return reply, err 290 | } 291 | 292 | // HKeys Returns all field names in the hash stored at key. 293 | func (ca *redisCache) HKeys(key string) ([]string, error) { 294 | client := ca.getReadRedisClient() 295 | reply, err := client.HKeys(key) 296 | if ca.checkConnErrorAndNeedRetry(err) { 297 | client = ca.getBackupRedis() 298 | return client.HKeys(key) 299 | } 300 | return reply, err 301 | } 302 | 303 | // HLen Returns the number of fields contained in the hash stored at key 304 | func (ca *redisCache) HLen(key string) (int, error) { 305 | client := ca.getReadRedisClient() 306 | reply, err := client.HLen(key) 307 | if ca.checkConnErrorAndNeedRetry(err) { 308 | client = ca.getBackupRedis() 309 | return client.HLen(key) 310 | } 311 | return reply, err 312 | } 313 | 314 | // HVals Returns all values in the hash stored at key 315 | func (ca *redisCache) HVals(key string) ([]string, error) { 316 | client := ca.getReadRedisClient() 317 | reply, err := client.HVals(key) 318 | if ca.checkConnErrorAndNeedRetry(err) { 319 | client = ca.getBackupRedis() 320 | return client.HVals(key) 321 | } 322 | return reply, err 323 | } 324 | 325 | /*---------- List -----------*/ 326 | // BLPop BLPOP is a blocking list pop primitive. 327 | // It is the blocking version of LPOP because it blocks the connection when there are no elements to pop from any of the given lists 328 | func (ca *redisCache) BLPop(key ...interface{}) (map[string]string, error) { 329 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 330 | reply, err := client.BLPop(key...) 331 | return reply, err 332 | } 333 | 334 | // BRPOP is a blocking list pop primitive 335 | // It is the blocking version of RPOP because it blocks the connection when there are no elements to pop from any of the given lists 336 | func (ca *redisCache) BRPop(key ...interface{}) (map[string]string, error) { 337 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 338 | reply, err := client.BRPop(key) 339 | return reply, err 340 | } 341 | 342 | // BRPOPLPUSH is a operation like RPOPLPUSH but blocking 343 | func (ca *redisCache) BRPopLPush(source string, destination string) (string, error) { 344 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 345 | reply, err := client.BRPopLPush(source, destination) 346 | return reply, err 347 | } 348 | 349 | // LIndex return element which subscript is index, 350 | // if index is -1, return last one element of list and so on 351 | func (ca *redisCache) LIndex(key string, index int) (string, error) { 352 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 353 | return client.LIndex(key, index) 354 | } 355 | 356 | // LInsert Inserts value in the list stored at key either before or after the reference value pivot. 357 | func (ca *redisCache) LInsert(key string, direction string, pivot string, value string) (int, error) { 358 | if direction != LInsert_Before && direction != LInsert_After { 359 | return -1, errors.New("direction only accept BEFORE or AFTER") 360 | } 361 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 362 | if direction == LInsert_Before { 363 | return client.LInsertBefore(key, pivot, value) 364 | } 365 | return client.LInsertAfter(key, pivot, value) 366 | 367 | } 368 | 369 | // LLen return length of list 370 | func (ca *redisCache) LLen(key string) (int, error) { 371 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 372 | return client.LLen(key) 373 | } 374 | 375 | // LPop remove and return head element of list 376 | func (ca *redisCache) LPop(key string) (string, error) { 377 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 378 | reply, err := client.LPop(key) 379 | return reply, err 380 | } 381 | 382 | // LPush Insert all the specified values at the head of the list stored at key 383 | func (ca *redisCache) LPush(key string, value ...interface{}) (int, error) { 384 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 385 | reply, err := client.LPush(key, value) 386 | return reply, err 387 | } 388 | 389 | // LPushX insert an element at the head of the list 390 | func (ca *redisCache) LPushX(key string, value string) (int, error) { 391 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 392 | reply, err := client.LPushX(key, value) 393 | return reply, err 394 | } 395 | 396 | // LRange Returns the specified elements of the list stored at key 397 | func (ca *redisCache) LRange(key string, start int, stop int) ([]string, error) { 398 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 399 | return client.LRange(key, start, stop) 400 | } 401 | 402 | // LRem Removes the first count occurrences of elements equal to value from the list stored at key. 403 | func (ca *redisCache) LRem(key string, count int, value string) (int, error) { 404 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 405 | reply, err := client.LRem(key, count, value) 406 | return reply, err 407 | } 408 | 409 | // LSet Sets the list element at index to value 410 | func (ca *redisCache) LSet(key string, index int, value string) (string, error) { 411 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 412 | reply, err := client.LSet(key, index, value) 413 | return reply, err 414 | } 415 | 416 | // LTrim Trim an existing list so that it will contain only the specified range of elements specified 417 | func (ca *redisCache) LTrim(key string, start int, stop int) (string, error) { 418 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 419 | reply, err := client.LTrim(key, start, stop) 420 | return reply, err 421 | } 422 | 423 | // RPop Removes and returns the last element of the list stored at key 424 | func (ca *redisCache) RPop(key string) (string, error) { 425 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 426 | reply, err := client.RPop(key) 427 | return reply, err 428 | } 429 | 430 | // RPopLPush Atomically returns and removes the last element (tail) of the list stored at source, and pushes the element at the first element (head) of the list stored at destination 431 | func (ca *redisCache) RPopLPush(source string, destination string) (string, error) { 432 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 433 | reply, err := client.RPopLPush(source, destination) 434 | return reply, err 435 | } 436 | 437 | // RPush Insert all the specified values at the tail of the list stored at key. 438 | func (ca *redisCache) RPush(key string, value ...interface{}) (int, error) { 439 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 440 | reply, err := client.RPush(key, value...) 441 | return reply, err 442 | } 443 | 444 | // RPushX Inserts value at the tail of the list stored at key, only if key already exists and holds a list 445 | func (ca *redisCache) RPushX(key string, value ...interface{}) (int, error) { 446 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 447 | reply, err := client.RPushX(key, value...) 448 | return reply, err 449 | } 450 | 451 | /*---------- Set -----------*/ 452 | // SAdd Add the specified members to the set stored at key 453 | func (ca *redisCache) SAdd(key string, member ...interface{}) (int, error) { 454 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 455 | reply, err := client.SAdd(key, member) 456 | return reply, err 457 | } 458 | 459 | // SCard Returns the set cardinality (number of elements) of the set stored at key 460 | func (ca *redisCache) SCard(key string) (int, error) { 461 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 462 | reply, err := client.SCard(key) 463 | return reply, err 464 | } 465 | 466 | // SDiff Returns the members of the set resulting from the difference between the first set and all the successive sets 467 | func (ca *redisCache) SDiff(key ...interface{}) ([]string, error) { 468 | client := ca.getReadRedisClient() 469 | reply, err := client.SDiff(key...) 470 | if ca.checkConnErrorAndNeedRetry(err) { 471 | client = ca.getBackupRedis() 472 | return client.SDiff(key...) 473 | } 474 | return reply, err 475 | } 476 | 477 | // SDiffStore This command is equal to SDIFF, but instead of returning the resulting set, it is stored in destination 478 | func (ca *redisCache) SDiffStore(destination string, key ...interface{}) (int, error) { 479 | client := ca.getReadRedisClient() 480 | reply, err := client.SDiffStore(destination, key...) 481 | if ca.checkConnErrorAndNeedRetry(err) { 482 | client = ca.getBackupRedis() 483 | return client.SDiffStore(destination, key...) 484 | } 485 | return reply, err 486 | } 487 | 488 | // SInter Returns the members of the set resulting from the intersection of all the given sets. 489 | func (ca *redisCache) SInter(key ...interface{}) ([]string, error) { 490 | client := ca.getReadRedisClient() 491 | reply, err := client.SInter(key...) 492 | if ca.checkConnErrorAndNeedRetry(err) { 493 | client = ca.getBackupRedis() 494 | return client.SInter(key...) 495 | } 496 | return reply, err 497 | } 498 | 499 | // SInterStore This command is equal to SINTER, but instead of returning the resulting set, it is stored in destination 500 | func (ca *redisCache) SInterStore(destination string, key ...interface{}) (int, error) { 501 | client := ca.getReadRedisClient() 502 | reply, err := client.SInterStore(destination, key...) 503 | if ca.checkConnErrorAndNeedRetry(err) { 504 | client = ca.getBackupRedis() 505 | return client.SInterStore(destination, key...) 506 | } 507 | return reply, err 508 | } 509 | 510 | // SIsMember Returns if member is a member of the set stored at key. 511 | func (ca *redisCache) SIsMember(key string, member string) (bool, error) { 512 | client := ca.getReadRedisClient() 513 | reply, err := client.SIsMember(key, member) 514 | if ca.checkConnErrorAndNeedRetry(err) { 515 | client = ca.getBackupRedis() 516 | return client.SIsMember(key, member) 517 | } 518 | return reply, err 519 | } 520 | 521 | // SMembers Returns all the members of the set value stored at key. 522 | func (ca *redisCache) SMembers(key string) ([]string, error) { 523 | client := ca.getReadRedisClient() 524 | reply, err := client.SMembers(key) 525 | if ca.checkConnErrorAndNeedRetry(err) { 526 | client = ca.getBackupRedis() 527 | return client.SMembers(key) 528 | } 529 | return reply, err 530 | } 531 | 532 | // SMove Move member from the set at source to the set at destination 533 | func (ca *redisCache) SMove(source string, destination string, member string) (bool, error) { 534 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 535 | return client.SMove(source, destination, member) 536 | } 537 | 538 | // SPop Removes and returns one or more random elements from the set value store at key. 539 | func (ca *redisCache) SPop(key string) (string, error) { 540 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 541 | return client.SPop(key) 542 | } 543 | 544 | // SRandMember When called with just the key argument, return a random element from the set value stored at key 545 | func (ca *redisCache) SRandMember(key string, count int) ([]string, error) { 546 | client := ca.getReadRedisClient() 547 | reply, err := client.SRandMember(key, count) 548 | if ca.checkConnErrorAndNeedRetry(err) { 549 | client = ca.getBackupRedis() 550 | return client.SRandMember(key, count) 551 | } 552 | return reply, err 553 | } 554 | 555 | // SRem Remove the specified members from the set stored at key 556 | func (ca *redisCache) SRem(key string, member ...interface{}) (int, error) { 557 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 558 | return client.SRem(key, member...) 559 | } 560 | 561 | // SUnion Returns the members of the set resulting from the union of all the given sets 562 | func (ca *redisCache) SUnion(key ...interface{}) ([]string, error) { 563 | client := ca.getReadRedisClient() 564 | reply, err := client.SUnion(key...) 565 | if ca.checkConnErrorAndNeedRetry(err) { 566 | client = ca.getBackupRedis() 567 | return client.SUnion(key...) 568 | } 569 | return reply, err 570 | } 571 | 572 | // SUnionStore This command is equal to SUNION, but instead of returning the resulting set, it is stored in destination 573 | func (ca *redisCache) SUnionStore(destination string, key ...interface{}) (int, error) { 574 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 575 | reply, err := client.SUnionStore(destination, key...) 576 | if ca.checkConnErrorAndNeedRetry(err) { 577 | client = ca.getBackupRedis() 578 | return client.SUnionStore(destination, key...) 579 | } 580 | return reply, err 581 | } 582 | 583 | //****************** sorted set 集合 *********************** 584 | // ZAdd Adds all the specified members with the specified scores to the sorted set stored at key 585 | func (ca *redisCache) ZAdd(key string, score int64, member interface{}) (int, error) { 586 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 587 | return client.ZAdd(key, score, member) 588 | } 589 | 590 | // ZCount Returns the number of elements in the sorted set at key with a score between min and max 591 | func (ca *redisCache) ZCount(key string, min, max int64) (int, error) { 592 | client := ca.getReadRedisClient() 593 | reply, err := client.ZCount(key, min, max) 594 | if ca.checkConnErrorAndNeedRetry(err) { 595 | client = ca.getBackupRedis() 596 | return client.ZCount(key, min, max) 597 | } 598 | return reply, err 599 | } 600 | 601 | // ZRem Removes the specified members from the sorted set stored at key. Non existing members are ignored. 602 | func (ca *redisCache) ZRem(key string, member ...interface{}) (int, error) { 603 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 604 | return client.ZRem(key, member...) 605 | } 606 | 607 | // ZCard Returns the sorted set cardinality (number of elements) of the sorted set stored at key. 608 | func (ca *redisCache) ZCard(key string) (int, error) { 609 | client := ca.getReadRedisClient() 610 | reply, err := client.ZCard(key) 611 | if ca.checkConnErrorAndNeedRetry(err) { 612 | client = ca.getBackupRedis() 613 | return client.ZCard(key) 614 | } 615 | return reply, err 616 | } 617 | 618 | // ZRank Returns the rank of member in the sorted set stored at key, with the scores ordered from low to high 619 | func (ca *redisCache) ZRank(key, member string) (int, error) { 620 | client := ca.getReadRedisClient() 621 | reply, err := client.ZRank(key, member) 622 | if ca.checkConnErrorAndNeedRetry(err) { 623 | client = ca.getBackupRedis() 624 | return client.ZRank(key, member) 625 | } 626 | return reply, err 627 | } 628 | 629 | // ZRange Returns the specified range of elements in the sorted set stored at key 630 | func (ca *redisCache) ZRange(key string, start, stop int64) ([]string, error) { 631 | client := ca.getReadRedisClient() 632 | reply, err := client.ZRange(key, start, stop) 633 | if ca.checkConnErrorAndNeedRetry(err) { 634 | client = ca.getBackupRedis() 635 | return client.ZRange(key, start, stop) 636 | } 637 | return reply, err 638 | } 639 | 640 | // ZRangeByScore Returns all the elements in the sorted set at key with a score between min and max (including elements with score equal to min or max). 641 | func (ca *redisCache) ZRangeByScore(key string, start, stop string, isWithScores bool) ([]string, error) { 642 | client := ca.getReadRedisClient() 643 | reply, err := client.ZRangeByScore(key, start, stop, isWithScores) 644 | if ca.checkConnErrorAndNeedRetry(err) { 645 | client = ca.getBackupRedis() 646 | return client.ZRangeByScore(key, start, stop, isWithScores) 647 | } 648 | return reply, err 649 | } 650 | 651 | // ZREVRangeByScore Returns all the elements in the sorted set at key with a score between max and min (including elements with score equal to max or min). In contrary to the default ordering of sorted sets, for this command the elements are considered to be ordered from high to low scores. 652 | func (ca *redisCache) ZREVRangeByScore(key string, max, min string, isWithScores bool) ([]string, error) { 653 | client := ca.getReadRedisClient() 654 | reply, err := client.ZREVRangeByScore(key, max, min, isWithScores) 655 | if ca.checkConnErrorAndNeedRetry(err) { 656 | client = ca.getBackupRedis() 657 | return client.ZREVRangeByScore(key, max, min, isWithScores) 658 | } 659 | return reply, err 660 | } 661 | 662 | // ZRange Returns the specified range of elements in the sorted set stored at key 663 | func (ca *redisCache) ZRevRange(key string, start, stop int64) ([]string, error) { 664 | client := ca.getReadRedisClient() 665 | reply, err := client.ZRevRange(key, start, stop) 666 | if ca.checkConnErrorAndNeedRetry(err) { 667 | client = ca.getBackupRedis() 668 | return client.ZRevRange(key, start, stop) 669 | } 670 | return reply, err 671 | } 672 | 673 | //****************** PUB/SUB ********************* 674 | // Publish Posts a message to the given channel. 675 | func (ca *redisCache) Publish(channel string, message interface{}) (int64, error) { 676 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 677 | return client.Publish(channel, message) 678 | } 679 | 680 | // Subscribe Subscribes the client to the specified channels 681 | func (ca *redisCache) Subscribe(receive chan Message, channels ...interface{}) error { 682 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 683 | conn := client.GetConn() 684 | psc := redis.PubSubConn{Conn: conn} 685 | defer func() { 686 | conn.Close() 687 | psc.Unsubscribe(channels...) 688 | }() 689 | 690 | err := psc.Subscribe(channels...) 691 | if err != nil { 692 | return err 693 | } 694 | for { 695 | switch v := psc.Receive().(type) { 696 | case redis.Message: 697 | fmt.Printf("%s: messages: %s\n", v.Channel, v.Data) 698 | receive <- Message{Channel: v.Channel, Data: v.Data} 699 | case redis.Subscription: 700 | fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) 701 | continue 702 | case error: 703 | return err 704 | } 705 | } 706 | } 707 | 708 | //****************** lua scripts ********************* 709 | // EVAL used to evaluate scripts using the Lua interpreter built into Redis starting from version 2.6.0 710 | func (ca *redisCache) EVAL(script string, argsNum int, arg ...interface{}) (interface{}, error) { 711 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 712 | return client.EVAL(script, argsNum, arg...) 713 | } 714 | 715 | //****************** 全局操作 *********************** 716 | // Ping ping command, if success return pong 717 | func (ca *redisCache) Ping() (string, error) { 718 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 719 | return client.Ping() 720 | } 721 | 722 | // ClearAll will delete all item in redis cache. 723 | // never error 724 | func (ca *redisCache) ClearAll() error { 725 | client := internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 726 | client.FlushDB() 727 | return nil 728 | } 729 | 730 | // getReadRedisClient get read mode redis client 731 | func (ca *redisCache) getReadRedisClient() *internal.RedisClient { 732 | if ca.hystrix.IsHystrix() { 733 | if ca.backupServerUrl != "" { 734 | return ca.getBackupRedis() 735 | } 736 | } 737 | if ca.readOnlyServerUrl != "" { 738 | return ca.getReadOnlyRedis() 739 | } 740 | return ca.getDefaultRedis() 741 | } 742 | 743 | // getRedisClient get default redis client 744 | func (ca *redisCache) getDefaultRedis() *internal.RedisClient { 745 | return internal.GetRedisClient(ca.serverUrl, ca.maxIdle, ca.maxActive) 746 | } 747 | 748 | func (ca *redisCache) getBackupRedis() *internal.RedisClient { 749 | return internal.GetRedisClient(ca.backupServerUrl, ca.backupMaxIdle, ca.backupMaxActive) 750 | } 751 | 752 | func (ca *redisCache) getReadOnlyRedis() *internal.RedisClient { 753 | return internal.GetRedisClient(ca.readOnlyServerUrl, ca.readOnlyMaxIdle, ca.readOnlyMaxActive) 754 | } 755 | 756 | // checkConnErrorAndNeedRetry check err is Conn error and is need to retry 757 | // if current client is hystrix, no need retry, because in getReadRedisClient already use backUp redis 758 | func (ca *redisCache) checkConnErrorAndNeedRetry(err error) bool { 759 | if err == nil { 760 | return false 761 | } 762 | if strings.Index(err.Error(), "no such host") >= 0 || 763 | strings.Index(err.Error(), "No connection could be made because the target machine actively refused it") >= 0 || 764 | strings.Index(err.Error(), "A connection attempt failed because the connected party did not properly respond after a period of time") >= 0 { 765 | ca.hystrix.GetCounter().Inc(1) 766 | //if is hystrix, not to retry, because in getReadRedisClient already use backUp redis 767 | if ca.hystrix.IsHystrix() { 768 | return false 769 | } 770 | if ca.backupServerUrl == "" { 771 | return false 772 | } 773 | return true 774 | } 775 | return false 776 | } 777 | 778 | // checkRedisAlive check redis is alive use ping 779 | // if set readonly redis, check readonly redis 780 | // if not set readonly redis, check default redis 781 | func (ca *redisCache) checkRedisAlive() bool { 782 | isAlive := false 783 | var redisClient *internal.RedisClient 784 | if ca.readOnlyServerUrl != "" { 785 | redisClient = ca.getReadOnlyRedis() 786 | } else { 787 | redisClient = ca.getDefaultRedis() 788 | } 789 | for i := 0; i <= 5; i++ { 790 | reply, err := redisClient.Ping() 791 | //fmt.Println(time.Now(), "checkAliveDefaultRedis Ping", reply, err) 792 | if err != nil { 793 | isAlive = false 794 | break 795 | } 796 | if reply != "PONG" { 797 | isAlive = false 798 | break 799 | } 800 | isAlive = true 801 | continue 802 | } 803 | return isAlive 804 | } 805 | -------------------------------------------------------------------------------- /redis/cache_redis_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var rc *redisCache 9 | 10 | func init() { 11 | rc = NewRedisCache("redis://192.168.8.175:6379/0", 20, 20) 12 | } 13 | 14 | func TestRedisCache_Publish(t *testing.T) { 15 | fmt.Println(rc.Publish("channel-test", "test message")) 16 | } 17 | 18 | func TestRedisCache_Subscribe(t *testing.T) { 19 | receive := make(chan Message, 1000) 20 | err := rc.Subscribe(receive, "channel-test") 21 | if err != nil{ 22 | t.Error("TestRedisCache_Subscribe error", err) 23 | } 24 | for{ 25 | select{ 26 | case msg := <- receive: 27 | fmt.Println(msg.Channel, msg.Data) 28 | } 29 | } 30 | } 31 | 32 | func TestRedisCache_Ping(t *testing.T) { 33 | fmt.Println(rc.Ping) 34 | } 35 | 36 | func TestRedisCache_ZAdd(t *testing.T){ 37 | fmt.Println(rc.ZAdd("dottest", 1, 1)) 38 | } 39 | 40 | func TestRedisCache_Set(t *testing.T) { 41 | fmt.Println(rc.Set("dottest", 1, 0)) 42 | } 43 | 44 | func TestRedisCache_ZCount(t *testing.T){ 45 | rc.ZAdd("dottest", 1, 1) 46 | rc.ZAdd("dottest", 2, 2) 47 | rc.ZAdd("dottest", 3, 3) 48 | rc.ZAdd("dottest", 10,10) 49 | rc.ZAdd("dottest", 11, 11) 50 | fmt.Println(rc.ZCount("dottest", 1, 10)) 51 | } 52 | 53 | func TestRedisCache_HDel(t *testing.T) { 54 | fmt.Println(rc.HDel("h1", "f11", "hf1")) 55 | } 56 | 57 | func TestRedisCache_HGet(t *testing.T) { 58 | fmt.Println(rc.HGet("h1", "hf2")) 59 | } 60 | 61 | func TestRedisCache_HExists(t *testing.T) { 62 | fmt.Println(rc.HExists("hkey1", "hkey1field1")) 63 | } 64 | 65 | func TestRedisCache_HIncrBy(t *testing.T) { 66 | fmt.Println(rc.HIncrBy("hkey1", "hint1", 1)) 67 | } 68 | func TestRedisCache_HIncrByFloat(t *testing.T) { 69 | fmt.Println(rc.HIncrByFloat("hkey1","hfloat1", 0.01)) 70 | } 71 | 72 | func TestRedisCache_HKeys(t *testing.T) { 73 | fmt.Println(rc.HKeys("hkey1")) 74 | } 75 | 76 | func TestRedisCache_HLen(t *testing.T) { 77 | fmt.Println(rc.HLen("hkey1")) 78 | } 79 | 80 | func TestRedisCache_HVals(t *testing.T) { 81 | fmt.Println(rc.HVals("hkey1")) 82 | } 83 | 84 | func TestRedisCache_HSet(t *testing.T) { 85 | fmt.Println(rc.HSet("hkey1", "hkey1field1", "hkey1field1value")) 86 | fmt.Println(rc.HSet("hkey1", "hkey1field2", "hkey1field2value")) 87 | } 88 | 89 | func TestRedisCache_HSetNX(t *testing.T) { 90 | fmt.Println(rc.HSetNX("hkey1", "hkey1field1", "hkey1field1value")) 91 | } 92 | -------------------------------------------------------------------------------- /runtime/cache_runtime.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | var ( 12 | // DefaultGCInterval means gc interval. 13 | DefaultGCInterval = 60 * time.Second // 1 minute 14 | ZeroInt64 int64 = 0 15 | ) 16 | 17 | // RuntimeItem store runtime cache item. 18 | type RuntimeItem struct { 19 | value interface{} 20 | createTime time.Time 21 | ttl time.Duration 22 | } 23 | 24 | //check item is expire 25 | func (mi *RuntimeItem) isExpire() bool { 26 | // 0 means forever 27 | if mi.ttl == 0 { 28 | return false 29 | } 30 | return time.Now().Sub(mi.createTime) > mi.ttl 31 | } 32 | 33 | // RuntimeCache is runtime cache adapter. 34 | // it contains a RW locker for safe map storage. 35 | type RuntimeCache struct { 36 | sync.RWMutex 37 | gcInterval time.Duration 38 | items map[string]*RuntimeItem 39 | } 40 | 41 | // NewRuntimeCache returns a new *RuntimeCache. 42 | func NewRuntimeCache() *RuntimeCache { 43 | cache := &RuntimeCache{items: make(map[string]*RuntimeItem), gcInterval: DefaultGCInterval} 44 | go cache.gc() 45 | return cache 46 | } 47 | 48 | // Get cache from runtime cache. 49 | // if non-existed or expired, return nil. 50 | func (ca *RuntimeCache) Get(key string) (interface{}, error) { 51 | ca.RLock() 52 | defer ca.RUnlock() 53 | if item, ok := ca.items[key]; ok { 54 | if item.isExpire() { 55 | return nil, nil 56 | } 57 | return item.value, nil 58 | } 59 | return nil, nil 60 | } 61 | 62 | // returns value string format by given key 63 | // if non-existed or expired, return "". 64 | func (ca *RuntimeCache) GetString(key string) (string, error) { 65 | v, err := ca.Get(key) 66 | if err != nil || v == nil { 67 | return "", nil 68 | } else { 69 | return fmt.Sprint(v), nil 70 | } 71 | } 72 | 73 | // returns value int format by given key 74 | // if non-existed or expired, return 0. 75 | func (ca *RuntimeCache) GetInt(key string) (int, error) { 76 | v, err := ca.GetString(key) 77 | if err != nil || v == "" { 78 | return 0, nil 79 | } else { 80 | i, e := strconv.Atoi(v) 81 | if e != nil { 82 | return 0, nil 83 | } else { 84 | return i, nil 85 | } 86 | } 87 | } 88 | 89 | // returns value int64 format by given key 90 | // if non-existed or expired, return 0. 91 | func (ca *RuntimeCache) GetInt64(key string) (int64, error) { 92 | v, err := ca.GetString(key) 93 | if err != nil || v == "" { 94 | return ZeroInt64, nil 95 | } else { 96 | i, e := strconv.ParseInt(v, 10, 64) 97 | if e != nil { 98 | return ZeroInt64, nil 99 | } else { 100 | return i, nil 101 | } 102 | } 103 | } 104 | 105 | // Set cache to runtime. 106 | // ttl is second, if ttl is 0, it will be forever till restart. 107 | func (ca *RuntimeCache) Set(key string, value interface{}, ttl int64) error { 108 | ca.Lock() 109 | defer ca.Unlock() 110 | ca.items[key] = &RuntimeItem{ 111 | value: value, 112 | createTime: time.Now(), 113 | ttl: time.Duration(ttl) * time.Second, 114 | } 115 | return nil 116 | } 117 | 118 | // Incr increase int64 counter in runtime cache. 119 | func (ca *RuntimeCache) Incr(key string) (int64, error) { 120 | ca.RLock() 121 | item, ok := ca.items[key] 122 | ca.RUnlock() 123 | if !ok { 124 | //if not exists, auto set new with 0 125 | ca.Set(key, ZeroInt64, 0) 126 | //reload 127 | ca.RLock() 128 | item, _ = ca.items[key] 129 | ca.RUnlock() 130 | } 131 | 132 | switch item.value.(type) { 133 | case int: 134 | item.value = item.value.(int) + 1 135 | case int32: 136 | item.value = item.value.(int32) + 1 137 | case int64: 138 | item.value = item.value.(int64) + 1 139 | case uint: 140 | item.value = item.value.(uint) + 1 141 | case uint32: 142 | item.value = item.value.(uint32) + 1 143 | case uint64: 144 | item.value = item.value.(uint64) + 1 145 | default: 146 | return 0, errors.New("item val is not (u)int (u)int32 (u)int64") 147 | } 148 | 149 | val, _ := strconv.ParseInt(fmt.Sprint(item.value), 10, 64) 150 | return val, nil 151 | } 152 | 153 | // Decr decrease counter in runtime cache. 154 | func (ca *RuntimeCache) Decr(key string) (int64, error) { 155 | ca.RLock() 156 | item, ok := ca.items[key] 157 | ca.RUnlock() 158 | if !ok { 159 | //if not exists, auto set new with 0 160 | ca.Set(key, ZeroInt64, 0) 161 | //reload 162 | ca.RLock() 163 | item, _ = ca.items[key] 164 | ca.RUnlock() 165 | } 166 | switch item.value.(type) { 167 | case int: 168 | item.value = item.value.(int) - 1 169 | case int64: 170 | item.value = item.value.(int64) - 1 171 | case int32: 172 | item.value = item.value.(int32) - 1 173 | case uint: 174 | if item.value.(uint) > 0 { 175 | item.value = item.value.(uint) - 1 176 | } else { 177 | return 0, errors.New("item val is less than 0") 178 | } 179 | case uint32: 180 | if item.value.(uint32) > 0 { 181 | item.value = item.value.(uint32) - 1 182 | } else { 183 | return 0, errors.New("item val is less than 0") 184 | } 185 | case uint64: 186 | if item.value.(uint64) > 0 { 187 | item.value = item.value.(uint64) - 1 188 | } else { 189 | return 0, errors.New("item val is less than 0") 190 | } 191 | default: 192 | return 0, errors.New("item val is not int int64 int32") 193 | } 194 | val, _ := strconv.ParseInt(fmt.Sprint(item.value), 10, 64) 195 | return val, nil 196 | } 197 | 198 | // Exist check item exist in runtime cache. 199 | func (ca *RuntimeCache) Exists(key string) (bool, error) { 200 | ca.RLock() 201 | defer ca.RUnlock() 202 | if v, ok := ca.items[key]; ok { 203 | return !v.isExpire(), nil 204 | } 205 | return false, nil 206 | } 207 | 208 | // Delete item in runtime cacha. 209 | // if not exists, we think it's success 210 | func (ca *RuntimeCache) Delete(key string) error { 211 | ca.Lock() 212 | defer ca.Unlock() 213 | if _, ok := ca.items[key]; !ok { 214 | //if not exists, we think it's success 215 | return nil 216 | } 217 | delete(ca.items, key) 218 | if _, ok := ca.items[key]; ok { 219 | return errors.New("delete key error") 220 | } 221 | return nil 222 | } 223 | 224 | // Expire Set a timeout on key. After the timeout has expired, the key will automatically be deleted. 225 | // timeout time duration is second 226 | // if not exists key, return 0, nil 227 | func (ca *RuntimeCache) Expire(key string, timeOutSeconds int) (int, error){ 228 | item, ok := ca.getRuntimeItem(key) 229 | if !ok { 230 | return 0, nil 231 | }else{ 232 | item.ttl = time.Duration(timeOutSeconds) * time.Second 233 | ca.Lock() 234 | ca.items[key] = item 235 | ca.Unlock() 236 | return timeOutSeconds, nil 237 | } 238 | } 239 | 240 | // ClearAll will delete all item in runtime cache. 241 | func (ca *RuntimeCache) ClearAll() error { 242 | ca.Lock() 243 | defer ca.Unlock() 244 | ca.items = make(map[string]*RuntimeItem) 245 | return nil 246 | } 247 | 248 | func (ca *RuntimeCache) gc() { 249 | for { 250 | <-time.After(ca.gcInterval) 251 | if ca.items == nil { 252 | return 253 | } 254 | for name := range ca.items { 255 | ca.itemExpired(name) 256 | } 257 | } 258 | } 259 | 260 | // getRuntimeItem get RuntimeItem by key 261 | func (ca *RuntimeCache) getRuntimeItem(key string) (*RuntimeItem, bool){ 262 | ca.RLock() 263 | defer ca.RUnlock() 264 | item, ok := ca.items[key] 265 | return item, ok 266 | } 267 | 268 | // itemExpired returns true if an item is expired. 269 | func (ca *RuntimeCache) itemExpired(key string) bool { 270 | itm, ok := ca.getRuntimeItem(key) 271 | if !ok { 272 | return true 273 | } 274 | if itm.isExpire() { 275 | ca.Lock() 276 | delete(ca.items, key) 277 | ca.Unlock() 278 | return true 279 | } 280 | return false 281 | } 282 | -------------------------------------------------------------------------------- /runtime/cache_runtime_test.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | --------------------------------------------------------------------------------