github.com/wangyougui/gf/v2@v2.6.5/os/gcache/gcache_adapter_redis.go (about) 1 // Copyright 2020 gf Author(https://github.com/wangyougui/gf). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/wangyougui/gf. 6 7 package gcache 8 9 import ( 10 "context" 11 "time" 12 13 "github.com/wangyougui/gf/v2/container/gvar" 14 "github.com/wangyougui/gf/v2/database/gredis" 15 "github.com/wangyougui/gf/v2/util/gconv" 16 ) 17 18 // AdapterRedis is the gcache adapter implements using Redis server. 19 type AdapterRedis struct { 20 redis *gredis.Redis 21 } 22 23 // NewAdapterRedis creates and returns a new memory cache object. 24 func NewAdapterRedis(redis *gredis.Redis) Adapter { 25 return &AdapterRedis{ 26 redis: redis, 27 } 28 } 29 30 // Set sets cache with `key`-`value` pair, which is expired after `duration`. 31 // 32 // It does not expire if `duration` == 0. 33 // It deletes the keys of `data` if `duration` < 0 or given `value` is nil. 34 func (c *AdapterRedis) Set(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (err error) { 35 redisKey := gconv.String(key) 36 if value == nil || duration < 0 { 37 _, err = c.redis.Del(ctx, redisKey) 38 } else { 39 if duration == 0 { 40 _, err = c.redis.Set(ctx, redisKey, value) 41 } else { 42 _, err = c.redis.Set(ctx, redisKey, value, gredis.SetOption{TTLOption: gredis.TTLOption{PX: gconv.PtrInt64(duration.Milliseconds())}}) 43 } 44 } 45 return err 46 } 47 48 // SetMap batch sets cache with key-value pairs by `data` map, which is expired after `duration`. 49 // 50 // It does not expire if `duration` == 0. 51 // It deletes the keys of `data` if `duration` < 0 or given `value` is nil. 52 func (c *AdapterRedis) SetMap(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) error { 53 if len(data) == 0 { 54 return nil 55 } 56 // DEL. 57 if duration < 0 { 58 var ( 59 index = 0 60 keys = make([]string, len(data)) 61 ) 62 for k := range data { 63 keys[index] = gconv.String(k) 64 index += 1 65 } 66 _, err := c.redis.Del(ctx, keys...) 67 if err != nil { 68 return err 69 } 70 } 71 if duration == 0 { 72 err := c.redis.MSet(ctx, gconv.Map(data)) 73 if err != nil { 74 return err 75 } 76 } 77 if duration > 0 { 78 var err error 79 for k, v := range data { 80 if err = c.Set(ctx, k, v, duration); err != nil { 81 return err 82 } 83 } 84 } 85 return nil 86 } 87 88 // SetIfNotExist sets cache with `key`-`value` pair which is expired after `duration` 89 // if `key` does not exist in the cache. It returns true the `key` does not exist in the 90 // cache, and it sets `value` successfully to the cache, or else it returns false. 91 // 92 // It does not expire if `duration` == 0. 93 // It deletes the `key` if `duration` < 0 or given `value` is nil. 94 func (c *AdapterRedis) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (bool, error) { 95 var ( 96 err error 97 redisKey = gconv.String(key) 98 ) 99 // Execute the function and retrieve the result. 100 f, ok := value.(Func) 101 if !ok { 102 // Compatible with raw function value. 103 f, ok = value.(func(ctx context.Context) (value interface{}, err error)) 104 } 105 if ok { 106 if value, err = f(ctx); err != nil { 107 return false, err 108 } 109 } 110 // DEL. 111 if duration < 0 || value == nil { 112 var delResult int64 113 delResult, err = c.redis.Del(ctx, redisKey) 114 if err != nil { 115 return false, err 116 } 117 if delResult == 1 { 118 return true, err 119 } 120 return false, err 121 } 122 ok, err = c.redis.SetNX(ctx, redisKey, value) 123 if err != nil { 124 return ok, err 125 } 126 if ok && duration > 0 { 127 // Set the expiration. 128 _, err = c.redis.PExpire(ctx, redisKey, duration.Milliseconds()) 129 if err != nil { 130 return ok, err 131 } 132 return ok, err 133 } 134 return ok, err 135 } 136 137 // SetIfNotExistFunc sets `key` with result of function `f` and returns true 138 // if `key` does not exist in the cache, or else it does nothing and returns false if `key` already exists. 139 // 140 // The parameter `value` can be type of `func() interface{}`, but it does nothing if its 141 // result is nil. 142 // 143 // It does not expire if `duration` == 0. 144 // It deletes the `key` if `duration` < 0 or given `value` is nil. 145 func (c *AdapterRedis) SetIfNotExistFunc(ctx context.Context, key interface{}, f Func, duration time.Duration) (ok bool, err error) { 146 value, err := f(ctx) 147 if err != nil { 148 return false, err 149 } 150 return c.SetIfNotExist(ctx, key, value, duration) 151 } 152 153 // SetIfNotExistFuncLock sets `key` with result of function `f` and returns true 154 // if `key` does not exist in the cache, or else it does nothing and returns false if `key` already exists. 155 // 156 // It does not expire if `duration` == 0. 157 // It deletes the `key` if `duration` < 0 or given `value` is nil. 158 // 159 // Note that it differs from function `SetIfNotExistFunc` is that the function `f` is executed within 160 // writing mutex lock for concurrent safety purpose. 161 func (c *AdapterRedis) SetIfNotExistFuncLock(ctx context.Context, key interface{}, f Func, duration time.Duration) (ok bool, err error) { 162 value, err := f(ctx) 163 if err != nil { 164 return false, err 165 } 166 return c.SetIfNotExist(ctx, key, value, duration) 167 } 168 169 // Get retrieves and returns the associated value of given <key>. 170 // It returns nil if it does not exist or its value is nil. 171 func (c *AdapterRedis) Get(ctx context.Context, key interface{}) (*gvar.Var, error) { 172 return c.redis.Get(ctx, gconv.String(key)) 173 } 174 175 // GetOrSet retrieves and returns the value of `key`, or sets `key`-`value` pair and 176 // returns `value` if `key` does not exist in the cache. The key-value pair expires 177 // after `duration`. 178 // 179 // It does not expire if `duration` == 0. 180 // It deletes the `key` if `duration` < 0 or given `value` is nil, but it does nothing 181 // if `value` is a function and the function result is nil. 182 func (c *AdapterRedis) GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (result *gvar.Var, err error) { 183 result, err = c.Get(ctx, key) 184 if err != nil { 185 return nil, err 186 } 187 if result.IsNil() { 188 return gvar.New(value), c.Set(ctx, key, value, duration) 189 } 190 return 191 } 192 193 // GetOrSetFunc retrieves and returns the value of `key`, or sets `key` with result of 194 // function `f` and returns its result if `key` does not exist in the cache. The key-value 195 // pair expires after `duration`. 196 // 197 // It does not expire if `duration` == 0. 198 // It deletes the `key` if `duration` < 0 or given `value` is nil, but it does nothing 199 // if `value` is a function and the function result is nil. 200 func (c *AdapterRedis) GetOrSetFunc(ctx context.Context, key interface{}, f Func, duration time.Duration) (result *gvar.Var, err error) { 201 v, err := c.Get(ctx, key) 202 if err != nil { 203 return nil, err 204 } 205 if v.IsNil() { 206 value, err := f(ctx) 207 if err != nil { 208 return nil, err 209 } 210 if value == nil { 211 return nil, nil 212 } 213 return gvar.New(value), c.Set(ctx, key, value, duration) 214 } else { 215 return v, nil 216 } 217 } 218 219 // GetOrSetFuncLock retrieves and returns the value of `key`, or sets `key` with result of 220 // function `f` and returns its result if `key` does not exist in the cache. The key-value 221 // pair expires after `duration`. 222 // 223 // It does not expire if `duration` == 0. 224 // It deletes the `key` if `duration` < 0 or given `value` is nil, but it does nothing 225 // if `value` is a function and the function result is nil. 226 // 227 // Note that it differs from function `GetOrSetFunc` is that the function `f` is executed within 228 // writing mutex lock for concurrent safety purpose. 229 func (c *AdapterRedis) GetOrSetFuncLock(ctx context.Context, key interface{}, f Func, duration time.Duration) (result *gvar.Var, err error) { 230 return c.GetOrSetFunc(ctx, key, f, duration) 231 } 232 233 // Contains checks and returns true if `key` exists in the cache, or else returns false. 234 func (c *AdapterRedis) Contains(ctx context.Context, key interface{}) (bool, error) { 235 n, err := c.redis.Exists(ctx, gconv.String(key)) 236 if err != nil { 237 return false, err 238 } 239 return n > 0, nil 240 } 241 242 // Size returns the number of items in the cache. 243 func (c *AdapterRedis) Size(ctx context.Context) (size int, err error) { 244 n, err := c.redis.DBSize(ctx) 245 if err != nil { 246 return 0, err 247 } 248 return int(n), nil 249 } 250 251 // Data returns a copy of all key-value pairs in the cache as map type. 252 // Note that this function may lead lots of memory usage, you can implement this function 253 // if necessary. 254 func (c *AdapterRedis) Data(ctx context.Context) (map[interface{}]interface{}, error) { 255 // Keys. 256 keys, err := c.redis.Keys(ctx, "*") 257 if err != nil { 258 return nil, err 259 } 260 // Key-Value pairs. 261 var m map[string]*gvar.Var 262 m, err = c.redis.MGet(ctx, keys...) 263 if err != nil { 264 return nil, err 265 } 266 // Type converting. 267 data := make(map[interface{}]interface{}) 268 for k, v := range m { 269 data[k] = v.Val() 270 } 271 return data, nil 272 } 273 274 // Keys returns all keys in the cache as slice. 275 func (c *AdapterRedis) Keys(ctx context.Context, regexp ...string) ([]interface{}, error) { 276 pattern := "*" 277 if len(regexp) > 0 { 278 pattern = regexp[0] 279 } 280 keys, err := c.redis.Keys(ctx, pattern) 281 if err != nil { 282 return nil, err 283 } 284 return gconv.Interfaces(keys), nil 285 } 286 287 // Values returns all values in the cache as slice. 288 func (c *AdapterRedis) Values(ctx context.Context) ([]interface{}, error) { 289 // Keys. 290 keys, err := c.redis.Keys(ctx, "*") 291 if err != nil { 292 return nil, err 293 } 294 // Key-Value pairs. 295 var m map[string]*gvar.Var 296 m, err = c.redis.MGet(ctx, keys...) 297 if err != nil { 298 return nil, err 299 } 300 // Values. 301 var values []interface{} 302 for _, key := range keys { 303 if v := m[key]; !v.IsNil() { 304 values = append(values, v.Val()) 305 } 306 } 307 return values, nil 308 } 309 310 // Update updates the value of `key` without changing its expiration and returns the old value. 311 // The returned value `exist` is false if the `key` does not exist in the cache. 312 // 313 // It deletes the `key` if given `value` is nil. 314 // It does nothing if `key` does not exist in the cache. 315 func (c *AdapterRedis) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) { 316 var ( 317 v *gvar.Var 318 oldPTTL int64 319 redisKey = gconv.String(key) 320 ) 321 // TTL. 322 oldPTTL, err = c.redis.PTTL(ctx, redisKey) // update ttl -> pttl(millisecond) 323 if err != nil { 324 return 325 } 326 if oldPTTL == -2 || oldPTTL == 0 { 327 // It does not exist or expired. 328 return 329 } 330 // Check existence. 331 v, err = c.redis.Get(ctx, redisKey) 332 if err != nil { 333 return 334 } 335 oldValue = v 336 // DEL. 337 if value == nil { 338 _, err = c.redis.Del(ctx, redisKey) 339 if err != nil { 340 return 341 } 342 return 343 } 344 // Update the value. 345 if oldPTTL == -1 { 346 _, err = c.redis.Set(ctx, redisKey, value) 347 } else { 348 // update SetEX -> SET PX Option(millisecond) 349 // Starting with Redis version 2.6.12: Added the EX, PX, NX and XX options. 350 _, err = c.redis.Set(ctx, redisKey, value, gredis.SetOption{TTLOption: gredis.TTLOption{PX: gconv.PtrInt64(oldPTTL)}}) 351 } 352 return oldValue, true, err 353 } 354 355 // UpdateExpire updates the expiration of `key` and returns the old expiration duration value. 356 // 357 // It returns -1 and does nothing if the `key` does not exist in the cache. 358 // It deletes the `key` if `duration` < 0. 359 func (c *AdapterRedis) UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) { 360 var ( 361 v *gvar.Var 362 oldPTTL int64 363 redisKey = gconv.String(key) 364 ) 365 // TTL. 366 oldPTTL, err = c.redis.PTTL(ctx, redisKey) 367 if err != nil { 368 return 369 } 370 if oldPTTL == -2 || oldPTTL == 0 { 371 // It does not exist or expired. 372 oldPTTL = -1 373 return 374 } 375 oldDuration = time.Duration(oldPTTL) * time.Millisecond 376 // DEL. 377 if duration < 0 { 378 _, err = c.redis.Del(ctx, redisKey) 379 return 380 } 381 // Update the expiration. 382 if duration > 0 { 383 _, err = c.redis.PExpire(ctx, redisKey, duration.Milliseconds()) 384 } 385 // No expire. 386 if duration == 0 { 387 v, err = c.redis.Get(ctx, redisKey) 388 if err != nil { 389 return 390 } 391 _, err = c.redis.Set(ctx, redisKey, v.Val()) 392 } 393 return 394 } 395 396 // GetExpire retrieves and returns the expiration of `key` in the cache. 397 // 398 // Note that, 399 // It returns 0 if the `key` does not expire. 400 // It returns -1 if the `key` does not exist in the cache. 401 func (c *AdapterRedis) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) { 402 pttl, err := c.redis.PTTL(ctx, gconv.String(key)) 403 if err != nil { 404 return 0, err 405 } 406 switch pttl { 407 case -1: 408 return 0, nil 409 case -2, 0: // It does not exist or expired. 410 return -1, nil 411 default: 412 return time.Duration(pttl) * time.Millisecond, nil 413 } 414 } 415 416 // Remove deletes the one or more keys from cache, and returns its value. 417 // If multiple keys are given, it returns the value of the deleted last item. 418 func (c *AdapterRedis) Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) { 419 if len(keys) == 0 { 420 return nil, nil 421 } 422 // Retrieves the last key value. 423 if lastValue, err = c.redis.Get(ctx, gconv.String(keys[len(keys)-1])); err != nil { 424 return nil, err 425 } 426 // Deletes all given keys. 427 _, err = c.redis.Del(ctx, gconv.Strings(keys)...) 428 return 429 } 430 431 // Clear clears all data of the cache. 432 // Note that this function is sensitive and should be carefully used. 433 // It uses `FLUSHDB` command in redis server, which might be disabled in server. 434 func (c *AdapterRedis) Clear(ctx context.Context) (err error) { 435 // The "FLUSHDB" may not be available. 436 err = c.redis.FlushDB(ctx) 437 return 438 } 439 440 // Close closes the cache. 441 func (c *AdapterRedis) Close(ctx context.Context) error { 442 // It does nothing. 443 return nil 444 }