github.com/gogf/gf/v2@v2.7.4/os/gcache/gcache_adapter_redis.go (about) 1 // Copyright 2020 gf Author(https://github.com/gogf/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/gogf/gf. 6 7 package gcache 8 9 import ( 10 "context" 11 "time" 12 13 "github.com/gogf/gf/v2/container/gvar" 14 "github.com/gogf/gf/v2/database/gredis" 15 "github.com/gogf/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) ([]interface{}, error) { 276 keys, err := c.redis.Keys(ctx, "*") 277 if err != nil { 278 return nil, err 279 } 280 return gconv.Interfaces(keys), nil 281 } 282 283 // Values returns all values in the cache as slice. 284 func (c *AdapterRedis) Values(ctx context.Context) ([]interface{}, error) { 285 // Keys. 286 keys, err := c.redis.Keys(ctx, "*") 287 if err != nil { 288 return nil, err 289 } 290 // Key-Value pairs. 291 var m map[string]*gvar.Var 292 m, err = c.redis.MGet(ctx, keys...) 293 if err != nil { 294 return nil, err 295 } 296 // Values. 297 var values []interface{} 298 for _, key := range keys { 299 if v := m[key]; !v.IsNil() { 300 values = append(values, v.Val()) 301 } 302 } 303 return values, nil 304 } 305 306 // Update updates the value of `key` without changing its expiration and returns the old value. 307 // The returned value `exist` is false if the `key` does not exist in the cache. 308 // 309 // It deletes the `key` if given `value` is nil. 310 // It does nothing if `key` does not exist in the cache. 311 func (c *AdapterRedis) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) { 312 var ( 313 v *gvar.Var 314 oldPTTL int64 315 redisKey = gconv.String(key) 316 ) 317 // TTL. 318 oldPTTL, err = c.redis.PTTL(ctx, redisKey) // update ttl -> pttl(millisecond) 319 if err != nil { 320 return 321 } 322 if oldPTTL == -2 || oldPTTL == 0 { 323 // It does not exist or expired. 324 return 325 } 326 // Check existence. 327 v, err = c.redis.Get(ctx, redisKey) 328 if err != nil { 329 return 330 } 331 oldValue = v 332 // DEL. 333 if value == nil { 334 _, err = c.redis.Del(ctx, redisKey) 335 if err != nil { 336 return 337 } 338 return 339 } 340 // Update the value. 341 if oldPTTL == -1 { 342 _, err = c.redis.Set(ctx, redisKey, value) 343 } else { 344 // update SetEX -> SET PX Option(millisecond) 345 // Starting with Redis version 2.6.12: Added the EX, PX, NX and XX options. 346 _, err = c.redis.Set(ctx, redisKey, value, gredis.SetOption{TTLOption: gredis.TTLOption{PX: gconv.PtrInt64(oldPTTL)}}) 347 } 348 return oldValue, true, err 349 } 350 351 // UpdateExpire updates the expiration of `key` and returns the old expiration duration value. 352 // 353 // It returns -1 and does nothing if the `key` does not exist in the cache. 354 // It deletes the `key` if `duration` < 0. 355 func (c *AdapterRedis) UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) { 356 var ( 357 v *gvar.Var 358 oldPTTL int64 359 redisKey = gconv.String(key) 360 ) 361 // TTL. 362 oldPTTL, err = c.redis.PTTL(ctx, redisKey) 363 if err != nil { 364 return 365 } 366 if oldPTTL == -2 || oldPTTL == 0 { 367 // It does not exist or expired. 368 oldPTTL = -1 369 return 370 } 371 oldDuration = time.Duration(oldPTTL) * time.Millisecond 372 // DEL. 373 if duration < 0 { 374 _, err = c.redis.Del(ctx, redisKey) 375 return 376 } 377 // Update the expiration. 378 if duration > 0 { 379 _, err = c.redis.PExpire(ctx, redisKey, duration.Milliseconds()) 380 } 381 // No expire. 382 if duration == 0 { 383 v, err = c.redis.Get(ctx, redisKey) 384 if err != nil { 385 return 386 } 387 _, err = c.redis.Set(ctx, redisKey, v.Val()) 388 } 389 return 390 } 391 392 // GetExpire retrieves and returns the expiration of `key` in the cache. 393 // 394 // Note that, 395 // It returns 0 if the `key` does not expire. 396 // It returns -1 if the `key` does not exist in the cache. 397 func (c *AdapterRedis) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) { 398 pttl, err := c.redis.PTTL(ctx, gconv.String(key)) 399 if err != nil { 400 return 0, err 401 } 402 switch pttl { 403 case -1: 404 return 0, nil 405 case -2, 0: // It does not exist or expired. 406 return -1, nil 407 default: 408 return time.Duration(pttl) * time.Millisecond, nil 409 } 410 } 411 412 // Remove deletes the one or more keys from cache, and returns its value. 413 // If multiple keys are given, it returns the value of the deleted last item. 414 func (c *AdapterRedis) Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) { 415 if len(keys) == 0 { 416 return nil, nil 417 } 418 // Retrieves the last key value. 419 if lastValue, err = c.redis.Get(ctx, gconv.String(keys[len(keys)-1])); err != nil { 420 return nil, err 421 } 422 // Deletes all given keys. 423 _, err = c.redis.Del(ctx, gconv.Strings(keys)...) 424 return 425 } 426 427 // Clear clears all data of the cache. 428 // Note that this function is sensitive and should be carefully used. 429 // It uses `FLUSHDB` command in redis server, which might be disabled in server. 430 func (c *AdapterRedis) Clear(ctx context.Context) (err error) { 431 // The "FLUSHDB" may not be available. 432 err = c.redis.FlushDB(ctx) 433 return 434 } 435 436 // Close closes the cache. 437 func (c *AdapterRedis) Close(ctx context.Context) error { 438 // It does nothing. 439 return nil 440 }