Files
cache/redis/redigo.go

238 lines
4.8 KiB
Go

// Package redis is a simple redis cache implement.
// base on the package: https://github.com/gomodule/redigo
package redis
import (
"errors"
"fmt"
"time"
"github.com/gomodule/redigo/redis"
"github.com/gookit/cache"
)
// Name driver name
const Name = "redigo"
// RedisCache fallback alias
type RedisCache = Redigo
// Redigo driver definition.
// redigo doc link: https://pkg.go.dev/github.com/gomodule/redigo/redis#pkg-examples
type Redigo struct {
cache.BaseDriver
// redis connection pool
pool *redis.Pool
// info
url string
pwd string
dbNum int
}
// New redis cache
func New(url, pwd string, dbNum int) *Redigo {
rc := &Redigo{
url: url, pwd: pwd, dbNum: dbNum,
}
return rc
}
// Connect create and connect to redis server
func Connect(url, pwd string, dbNum int) *Redigo {
return New(url, pwd, dbNum).Connect()
}
// Connect to redis server
func (c *Redigo) Connect() *Redigo {
c.pool = newPool(c.url, c.pwd, c.dbNum)
c.Logf("connect to server %s db is %d", c.url, c.dbNum)
return c
}
/*************************************************************
* methods implements of the gsr.SimpleCacher
*************************************************************/
// Get value by key
func (c *Redigo) Get(key string) any {
bts, err := redis.Bytes(c.exec("Get", c.Key(key)))
return c.Unmarshal(bts, err)
}
// GetAs get cache and unmarshal to ptr
func (c *Redigo) GetAs(key string, ptr any) error {
bts, err := redis.Bytes(c.exec("Get", c.Key(key)))
if err != nil {
return err
}
return c.UnmarshalTo(bts, ptr)
}
// Set value by key
func (c *Redigo) Set(key string, val any, ttl time.Duration) (err error) {
val, err = c.Marshal(val)
if err != nil {
return err
}
_, err = c.exec("SetEx", c.Key(key), int64(ttl/time.Second), val)
return
}
// Del value by key
func (c *Redigo) Del(key string) (err error) {
_, err = c.exec("Del", c.Key(key))
return
}
// Has cache key
func (c *Redigo) Has(key string) bool {
// return 0 OR 1
one, err := redis.Int(c.exec("Exists", c.Key(key)))
c.SetLastErr(err)
return one == 1
}
// GetMulti values by keys
func (c *Redigo) GetMulti(keys []string) map[string]any {
conn := c.pool.Get()
defer conn.Close()
args := make([]any, 0, len(keys))
for _, key := range keys {
args = append(args, c.Key(key))
}
list, err := redis.Values(c.exec("MGet", args...))
if err != nil {
c.SetLastErr(err)
return nil
}
values := make(map[string]any, len(keys))
for i, val := range list {
values[keys[i]] = val
}
return values
}
// SetMulti values
func (c *Redigo) SetMulti(values map[string]any, ttl time.Duration) (err error) {
conn := c.pool.Get()
defer conn.Close()
// open multi
err = conn.Send("Multi")
if err != nil {
return err
}
ttlSec := int64(ttl / time.Second)
for key, val := range values {
// bs, _ := cache.Marshal(val)
conn.Send("SetEx", c.Key(key), ttlSec, val)
}
// do exec
_, err = redis.Ints(conn.Do("Exec"))
return
}
// DelMulti values by keys
func (c *Redigo) DelMulti(keys []string) (err error) {
args := make([]any, 0, len(keys))
for _, key := range keys {
args = append(args, c.Key(key))
}
_, err = c.exec("Del", args...)
return
}
// Close connection
func (c *Redigo) Close() error {
return c.pool.Close()
}
// Clear all caches
func (c *Redigo) Clear() error {
_, err := c.exec("FlushDb")
return err
}
/*************************************************************
* helper methods
*************************************************************/
// Pool get
func (c *Redigo) Pool() *redis.Pool {
return c.pool
}
// String get
func (c *Redigo) String() string {
pwd := "*"
if c.IsDebug() {
pwd = c.pwd
}
return fmt.Sprintf("connection info. url: %s, pwd: %s, dbNum: %d", c.url, pwd, c.dbNum)
}
// actually do the redis cmds, args[0] must be the key name.
func (c *Redigo) exec(commandName string, args ...any) (reply any, err error) {
if len(args) < 1 {
return nil, errors.New("missing required arguments")
}
conn := c.pool.Get()
defer conn.Close()
if c.IsDebug() {
st := time.Now()
reply, err = conn.Do(commandName, args...)
c.Logf(
"operate redis cache. command: %s, key: %v, elapsed time: %.03f\n",
commandName, args[0], time.Since(st).Seconds()*1000,
)
return
}
return conn.Do(commandName, args...)
}
// create new pool
func newPool(url, password string, dbNum int) *redis.Pool {
return &redis.Pool{
MaxIdle: 5,
// timeout
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", url)
if err != nil {
return nil, err
}
if password != "" {
_, err := c.Do("AUTH", password)
if err != nil {
_ = c.Close()
return nil, err
}
}
_, _ = c.Do("SELECT", dbNum)
return c, err
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
}