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  }