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  }