cache_redis.go 7.73 KiB
package cachemdl
/*
Provides cache access and manipulation methods for redis server
Implements cacher interface.
Official docs -
1. redis client - https://github.com/go-redis/redis
2. redis server - https://redis.io/
Note - corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl must be initialized before use
import (
	"encoding/json"
	"errors"
	"log"
	"strings"
	"time"
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl"
	"github.com/go-redis/redis"
const (
	noExp time.Duration = 0
	// NotOK refers to a unsuccessfull operations
	NotOK int64 = 0
	// OK refers to a successfull operations
	OK int64 = 1
	keySplitter = ":"
// RedisCache represents a Redis client with provided configuration. Do not change configuration at runtime.
type RedisCache struct {
	cli       *redis.Client  // represents redis client
	opt       *redis.Options //
	keyStr    string         // "<Prefix>:"
	addPrefix bool           //
	connected bool           // will be enabled if redis client connects to server
	Addr       string        // redis server address, default "127.0.0.1:6379"
	DB         int           // redis DB on provided server, default 0
	Password   string        //
	Expiration time.Duration // this duration will be used for Set() method
	Prefix     string        // this will be used for storing keys for provided project
type configRedis struct {
	addr       string        // redis server address, default "127.0.0.1:6379"
	db         int           // redis DB on provided server, default 0
	password   string        //
	expiration time.Duration // this duration will be used for Set() method
	prefix     string        // this will be used for storing keys for provided project
type redisOption func(*configRedis)
func RedisWithAddr(addr string) redisOption {
	return func(cfg *configRedis) {
		cfg.addr = addr
func RedisWithDB(db int) redisOption {
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
return func(cfg *configRedis) { cfg.db = db } } func RedisWithPrefix(pfx string) redisOption { return func(cfg *configRedis) { cfg.prefix = pfx } } func RedisWithPassword(p string) redisOption { return func(cfg *configRedis) { cfg.password = p } } func RedisWithExpiration(exp time.Duration) redisOption { return func(cfg *configRedis) { cfg.expiration = exp } } // Setup initializes redis cache for application. Must be called only once. func (rc *RedisCache) Setup(addr, password, prefix string, db int, exp time.Duration) { if rc == nil { rc = new(RedisCache) } rc.Addr = addr rc.Password = password rc.DB = db rc.Expiration = exp rc.Prefix = prefix opt := redis.Options{ Addr: addr, Password: password, DB: db, } rc.opt = &opt rc.cli = redis.NewClient(&opt) if _, err := rc.cli.Ping().Result(); err != nil { // exit if connection to redis server fails loggermdl.LogError("connection to redis server failed: ", err) log.Fatal("connection to redis server failed: ", err) } rc.connected = true if prefix != "" { rc.keyStr = contcat(rc.Prefix, keySplitter) rc.addPrefix = true } } // SetupRedisCache initializes redis cache for application and returns it. Must be called only once. func SetupRedisCache(opts ...redisOption) (*RedisCache, error) { rc := new(RedisCache) cfg := new(configRedis) for i := range opts { opts[i](cfg) } rc.Addr = cfg.addr rc.Password = cfg.password rc.DB = cfg.db
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
rc.Expiration = cfg.expiration rc.Prefix = cfg.prefix rc.opt = &redis.Options{ Addr: cfg.addr, Password: cfg.password, DB: cfg.db, } rc.cli = redis.NewClient(rc.opt) if _, err := rc.cli.Ping().Result(); err != nil { return nil, errors.New("connection to redis server failed: " + err.Error()) } rc.connected = true if cfg.prefix != "" { rc.keyStr = contcat(rc.Prefix, keySplitter) rc.addPrefix = true } return rc, nil } // Set marshalls provided value and stores against provided key. Errors will be logged to initialized logger. func (rc *RedisCache) Set(key string, val interface{}) { ba, err := marshalWithTypeCheck(val) if err != nil { loggermdl.LogError("error setting key ", key, " error: ", err) return } rc.cli.Set(rc.key(key), ba, rc.Expiration) } // SetWithExpiration marshalls provided value and stores against provided key for given duration. Errors will be logged to initialized logger. func (rc *RedisCache) SetWithExpiration(key string, val interface{}, exp time.Duration) { ba, err := marshalWithTypeCheck(val) if err != nil { loggermdl.LogError("error setting key ", key, " error: ", err) return } rc.cli.Set(rc.key(key), ba, exp) } // SetNoExpiration marshalls provided value and stores against provided key. // Errors will be logged to initialized logger. func (rc *RedisCache) SetNoExpiration(key string, val interface{}) { ba, err := marshalWithTypeCheck(val) if err != nil { loggermdl.LogError("error setting key ", key, " error: ", err) return } rc.cli.Set(rc.key(key), ba, noExp) } // Get returns data against provided key. The final result is parsed with gjson. Returns false if not present. func (rc *RedisCache) Get(key string) (interface{}, bool) { // exists, err := rc.cli.Exists(key).Result() // if err != nil { // loggermdl.LogError("error checking key ", key, " error: ", err) // return nil, false // } // if exists == NotOK { // return nil, false
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
// } // Get returns error if key is not present. val, err := rc.cli.Get(rc.key(key)).Result() if err != nil { loggermdl.LogError("error getting key", key, "from redis cache with error:", err) return nil, false } return val, true } // Delete - func (rc *RedisCache) Delete(key string) { rc.cli.Del(rc.key(key)).Result() } // GetItemsCount - func (rc *RedisCache) GetItemsCount() int { pattern := rc.Prefix + "*" keys, err := rc.cli.Keys(pattern).Result() if err != nil { loggermdl.LogError("error getting item count for ", pattern, " error: ", err) return 0 } return len(keys) } func (rc *RedisCache) flushDB() (string, error) { return rc.cli.FlushDB().Result() } // Purge deletes for current redis db func (rc *RedisCache) Purge() { _, err := rc.flushDB() if err != nil { loggermdl.LogError("error purging redis cache for db ", rc.Addr, "/", rc.DB, " error: ", err) } } func marshal(v interface{}) ([]byte, error) { return json.Marshal(v) } // marshalWithTypeCheck checks type before marshsal. Save allocations and time significantly if the existing data is string or []byte func marshalWithTypeCheck(v interface{}) ([]byte, error) { switch d := v.(type) { default: return json.Marshal(v) case string: return []byte(d), nil case []byte: return d, nil } } func contcat(s ...string) string { sb := strings.Builder{} for i := range s { sb.WriteString(s[i]) } return sb.String() } func (rc *RedisCache) key(key string) string { // prepare in format "<Prefix>:<key>" if rc.addPrefix { return contcat(rc.keyStr, key) }
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
return key } func (rc *RedisCache) Type() int { return TypeRedisCache } // GetAll returns all keys with values present in redis server. Excludes the keys which does not have specified prefix. If prefix is empty, then returns all keys. // // **This is not intended for production use. May hamper performance** func (rc *RedisCache) GetAll() map[string]interface{} { pattern := rc.Prefix + "*" keys, err := rc.cli.Keys(pattern).Result() if err != nil { loggermdl.LogError("error getting keys for ", pattern, " error: ", err) return nil } result := make(map[string]interface{}, len(keys)) for i := range keys { ba, err := rc.cli.Get(keys[i]).Bytes() if err != nil { loggermdl.LogError("error getting key", keys[i], "from redis cache with error:", err) continue } var val interface{} _ = json.Unmarshal(ba, &val) // accessing [1] won't panic as we have all keys with matching regex result[strings.Split(keys[i], keySplitter)[1]] = val } return result }