Newer
Older
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"
"time"
"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl"
"github.com/go-redis/redis"
)
const (
noExp time.Duration = 0
// RedisCache represents a Redis client with provided configuration. Do not change configuration at runtime.
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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 {
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
}
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// 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
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. Returns false if not present.
func (rc *RedisCache) Get(key string) (interface{}, bool) {
// Get returns error if key is not present.
val, err := rc.cli.Get(rc.key(key)).Result()
loggermdl.LogError("error getting key", key, "from redis cache with error:", err)
return nil, false
}
}
// 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(rc.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)
}
return key
}
func (rc *RedisCache) actualKey(key string) string {
if rc.addPrefix {
return strings.TrimPrefix(key, rc.keyStr)
}
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{} {
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)
// GetItemsCount -
func (rc *RedisCache) keys() []string {
pattern := rc.Prefix + "*"
keys, err := rc.cli.Keys(pattern).Result()
if err != nil {
loggermdl.LogError("error getting item count for ", pattern, " error: ", err)
}
return keys
}