code.gitea.io/gitea@v1.19.3/modules/cache/cache_redis.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package cache
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"time"
    10  
    11  	"code.gitea.io/gitea/modules/graceful"
    12  	"code.gitea.io/gitea/modules/nosql"
    13  
    14  	"gitea.com/go-chi/cache"
    15  	"github.com/redis/go-redis/v9"
    16  )
    17  
    18  // RedisCacher represents a redis cache adapter implementation.
    19  type RedisCacher struct {
    20  	c          redis.UniversalClient
    21  	prefix     string
    22  	hsetName   string
    23  	occupyMode bool
    24  }
    25  
    26  // toStr convert string/int/int64 interface to string. it's only used by the RedisCacher.Put internally
    27  func toStr(v interface{}) string {
    28  	if v == nil {
    29  		return ""
    30  	}
    31  	switch v := v.(type) {
    32  	case string:
    33  		return v
    34  	case []byte:
    35  		return string(v)
    36  	case int:
    37  		return strconv.FormatInt(int64(v), 10)
    38  	case int64:
    39  		return strconv.FormatInt(v, 10)
    40  	default:
    41  		return fmt.Sprint(v) // as what the old com.ToStr does in most cases
    42  	}
    43  }
    44  
    45  // Put puts value (string type) into cache with key and expire time.
    46  // If expired is 0, it lives forever.
    47  func (c *RedisCacher) Put(key string, val interface{}, expire int64) error {
    48  	// this function is not well-designed, it only puts string values into cache
    49  	key = c.prefix + key
    50  	if expire == 0 {
    51  		if err := c.c.Set(graceful.GetManager().HammerContext(), key, toStr(val), 0).Err(); err != nil {
    52  			return err
    53  		}
    54  	} else {
    55  		dur := time.Duration(expire) * time.Second
    56  		if err := c.c.Set(graceful.GetManager().HammerContext(), key, toStr(val), dur).Err(); err != nil {
    57  			return err
    58  		}
    59  	}
    60  
    61  	if c.occupyMode {
    62  		return nil
    63  	}
    64  	return c.c.HSet(graceful.GetManager().HammerContext(), c.hsetName, key, "0").Err()
    65  }
    66  
    67  // Get gets cached value by given key.
    68  func (c *RedisCacher) Get(key string) interface{} {
    69  	val, err := c.c.Get(graceful.GetManager().HammerContext(), c.prefix+key).Result()
    70  	if err != nil {
    71  		return nil
    72  	}
    73  	return val
    74  }
    75  
    76  // Delete deletes cached value by given key.
    77  func (c *RedisCacher) Delete(key string) error {
    78  	key = c.prefix + key
    79  	if err := c.c.Del(graceful.GetManager().HammerContext(), key).Err(); err != nil {
    80  		return err
    81  	}
    82  
    83  	if c.occupyMode {
    84  		return nil
    85  	}
    86  	return c.c.HDel(graceful.GetManager().HammerContext(), c.hsetName, key).Err()
    87  }
    88  
    89  // Incr increases cached int-type value by given key as a counter.
    90  func (c *RedisCacher) Incr(key string) error {
    91  	if !c.IsExist(key) {
    92  		return fmt.Errorf("key '%s' not exist", key)
    93  	}
    94  	return c.c.Incr(graceful.GetManager().HammerContext(), c.prefix+key).Err()
    95  }
    96  
    97  // Decr decreases cached int-type value by given key as a counter.
    98  func (c *RedisCacher) Decr(key string) error {
    99  	if !c.IsExist(key) {
   100  		return fmt.Errorf("key '%s' not exist", key)
   101  	}
   102  	return c.c.Decr(graceful.GetManager().HammerContext(), c.prefix+key).Err()
   103  }
   104  
   105  // IsExist returns true if cached value exists.
   106  func (c *RedisCacher) IsExist(key string) bool {
   107  	if c.c.Exists(graceful.GetManager().HammerContext(), c.prefix+key).Val() == 1 {
   108  		return true
   109  	}
   110  
   111  	if !c.occupyMode {
   112  		c.c.HDel(graceful.GetManager().HammerContext(), c.hsetName, c.prefix+key)
   113  	}
   114  	return false
   115  }
   116  
   117  // Flush deletes all cached data.
   118  func (c *RedisCacher) Flush() error {
   119  	if c.occupyMode {
   120  		return c.c.FlushDB(graceful.GetManager().HammerContext()).Err()
   121  	}
   122  
   123  	keys, err := c.c.HKeys(graceful.GetManager().HammerContext(), c.hsetName).Result()
   124  	if err != nil {
   125  		return err
   126  	}
   127  	if err = c.c.Del(graceful.GetManager().HammerContext(), keys...).Err(); err != nil {
   128  		return err
   129  	}
   130  	return c.c.Del(graceful.GetManager().HammerContext(), c.hsetName).Err()
   131  }
   132  
   133  // StartAndGC starts GC routine based on config string settings.
   134  // AdapterConfig: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,hset_name=MacaronCache,prefix=cache:
   135  func (c *RedisCacher) StartAndGC(opts cache.Options) error {
   136  	c.hsetName = "MacaronCache"
   137  	c.occupyMode = opts.OccupyMode
   138  
   139  	uri := nosql.ToRedisURI(opts.AdapterConfig)
   140  
   141  	c.c = nosql.GetManager().GetRedisClient(uri.String())
   142  
   143  	for k, v := range uri.Query() {
   144  		switch k {
   145  		case "hset_name":
   146  			c.hsetName = v[0]
   147  		case "prefix":
   148  			c.prefix = v[0]
   149  		}
   150  	}
   151  
   152  	return c.c.Ping(graceful.GetManager().HammerContext()).Err()
   153  }
   154  
   155  // Ping tests if the cache is alive.
   156  func (c *RedisCacher) Ping() error {
   157  	return c.c.Ping(graceful.GetManager().HammerContext()).Err()
   158  }
   159  
   160  func init() {
   161  	cache.Register("redis", &RedisCacher{})
   162  }