1 Star 1 Fork 0

Gousing/cache

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
driver_redis.go 18.93 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
package cache
import (
"context"
"errors"
"fmt"
"log/slog"
"math"
"net"
"strings"
"sync"
"sync/atomic"
"time"
//DOC https://redis.uptrace.dev/zh/
"github.com/redis/go-redis/v9"
)
type RedisCache struct {
client redis.UniversalClient
ctx context.Context
defaultExpire time.Duration // 默认过期时间 默认: 0 表示长久存储, 注意:最小过期时间为1s
tags *redisTags
tagsPool *sync.Pool // redisCacheTags对象池
hitCount int64
missCount int64
isGlobal bool // 是否为全局默认cache
}
// RedisOptions Redis缓存配置信息
type RedisOptions struct {
// Client Redis服务器链接选项,使用 UniversalClient 统一客户端,UniversalClient是对 Client 、 ClusterClient 、 FailoverClient 客户端的包装。
// - 根据不同的选项,客户端的类型如下:
// - 如果指定了 MasterName 选项,则返回 FailoverClient 哨兵客户端。
// - 如果 Addrs 是 2 个以上的地址,则返回 ClusterClient 集群客户端。
// - 其他情况,返回 Client 单节点客户端。
// - github.com/redis/go-redis/v9
// - https://redis.uptrace.dev/zh/guide/universal.html
// - RedisUniversalOptions is an alias for [redis.UniversalOptions]
Client RedisUniversalOptions
// DefaultExpire 默认过期时间(配置单位为[秒]) 0 表示不过期(实例生命周期内/长久存储) 注意:最小过期时间为1s time.Second
// FreeCache 的过期时间是一个近似值,实际过期时间会在 (X-1, X] 秒之间,其中 X 是你设置的过期时间。
DefaultExpire int
}
type RedisUniversalOptions = redis.UniversalOptions
func parseRedisOptions(ops RedisOptions) RedisOptions {
if len(ops.Client.Addrs) < 1 {
ops.Client.Addrs = []string{"localhost:6379"}
}
if ops.Client.DB < 0 {
ops.Client.DB = 0
} else if ops.Client.DB > 15 {
ops.Client.DB = 15
}
if ops.Client.MaxIdleConns < 0 {
ops.Client.MaxIdleConns = 0
}
if ops.Client.MaxActiveConns < 0 {
ops.Client.MaxActiveConns = 0
} else if ops.Client.MaxActiveConns != 0 && ops.Client.MaxActiveConns < ops.Client.MaxIdleConns {
ops.Client.MaxActiveConns = int(math.Ceil(1.25 * float64(ops.Client.MaxIdleConns)))
}
if ops.DefaultExpire < 1 {
ops.DefaultExpire = 0
}
return ops
}
// NewRedis
// - options RedisOptions
func NewRedis(options RedisOptions) (*RedisCache, error) {
ops := parseRedisOptions(options)
// 使用 UniversalClient 统一客户端,UniversalClient是对 Client 、 ClusterClient 、 FailoverClient 客户端的包装。
// - 根据不同的选项,客户端的类型如下:
// - 如果指定了 MasterName 选项,则返回 FailoverClient 哨兵客户端。
// - 如果 Addrs 是 2 个以上的地址,则返回 ClusterClient 集群客户端。
// - 其他情况,返回 Client 单节点客户端。
// - https://redis.uptrace.dev/zh/guide/universal.html
redisClient := redis.NewUniversalClient(&ops.Client)
// 验证是否连接到redis服务端
ctx := context.Background()
_, err := redisClient.Ping(ctx).Result()
if err != nil {
logWrapAttr(getEntryPC(2, nil, false), slog.LevelError, "NewRedis Error",
slog.String("Addrs", strings.Join(ops.Client.Addrs, ",")),
slog.Int("DB", ops.Client.DB),
slog.Int("MaxIdle", ops.Client.MaxIdleConns),
slog.Int("MaxActive", ops.Client.MaxActiveConns),
slog.String("Error", err.Error()))
return nil, errors.New("New NewClient Error, [Addrs]" + strings.Join(ops.Client.Addrs, ",") + " [Error]" + err.Error())
}
logWrapAttr(getEntryPC(2, nil, false), slog.LevelDebug, "NewRedis inited",
slog.String("Addrs", strings.Join(ops.Client.Addrs, ",")),
slog.Int("DB", ops.Client.DB),
slog.Int("MaxIdle", ops.Client.MaxIdleConns),
slog.Int("MaxActive", ops.Client.MaxActiveConns))
rc := &RedisCache{
client: redisClient,
ctx: ctx,
defaultExpire: time.Duration(ops.DefaultExpire) * time.Second,
}
rc.tags = newRedisTags(rc)
rc.tagsPool = &sync.Pool{
New: func() any {
return &redisCacheTags{cache: rc, tags: []string{}}
},
}
return rc, nil
}
func (s *RedisCache) _valMarshal(key string, val any) (valBytes []byte, err error) {
if val == nil {
logWrapAttr(getEntryPC(3, s, true), slog.LevelError, "RedisCache: value can not be nil", slog.String("key", key))
return nil, ErrValIsNilPointer
}
if valBytes, err = getJsoner().Marshal(val); err != nil {
logWrapAttr(getEntryPC(3, s, true), slog.LevelError, "RedisCache: val does not Marshal", slog.String("key", key), slog.String("error", err.Error()))
return nil, ErrValNotMarshal
} else {
return valBytes, nil
}
}
func (s *RedisCache) _valUnmarshal(key string, val []byte, refVal any) (err error) {
if refVal == nil {
logWrapAttr(getEntryPC(3, s, true), slog.LevelError, "RedisCache: refVal can not be nil", slog.String("key", key))
return ErrValIsNilPointer
}
if err = getJsoner().Unmarshal(val, refVal); err != nil {
logWrapAttr(getEntryPC(3, s, true), slog.LevelError, "RedisCache: val does not Unmarshal", slog.String("key", key), slog.String("error", err.Error()))
return ErrValNotUnmarshal
}
return nil
}
// Tags 入口
func (s *RedisCache) Tags(tags ...string) TagsIO {
//return &redisCacheTags{cache: s, tags: tags}
t := s.tagsPool.Get().(*redisCacheTags)
t.tags = t.tags[:0]
t.tags = tags[:]
s.tagsPool.Put(t)
return t
}
// Has 判断缓存是否存在
func (s *RedisCache) Has(key string) bool {
val, err := s.client.Exists(s.ctx, key).Result()
if err != nil {
return false
}
return val == 1
}
// TTL 获取缓存过期时间 (返回0 表示不过期(实例生命周期内/长久存储) 注意:最小过期时间为1s time.Second)
func (s *RedisCache) TTL(key string) (time.Duration, error) {
if key == "" {
return 0, ErrKeyCanNotEmpty
}
if _, err := s.client.Exists(s.ctx, key).Result(); err != nil {
return 0, ErrKeyNotExist
}
if ttl, err := s.client.TTL(s.ctx, key).Result(); err != nil {
return 0, err
} else {
return ttl, nil
}
}
func (s *RedisCache) _read(key string) ([]byte, error) {
if key == "" {
return nil, ErrKeyCanNotEmpty
}
val, err := s.client.Get(s.ctx, key).Bytes()
if err != nil {
if err == redis.Nil {
atomic.AddInt64(&s.missCount, 1)
return nil, ErrKeyNotExist
}
return nil, err
}
atomic.AddInt64(&s.hitCount, 1)
return val, nil
}
func _redisCacheReadAny[T any](s *RedisCache, key string) (refVal T, err error) {
if s == nil {
err = ErrStoreInstanceIsNil
return
}
var val []byte
val, err = s._read(key)
if val == nil || err != nil {
return
}
if err = s._valUnmarshal(key, val, &refVal); err != nil {
return
}
return refVal, nil
}
func _redisCacheReadDefault[T any](s *RedisCache, key string, defaultVal T) T {
if s == nil || key == "" {
return defaultVal
}
if val, err := _redisCacheReadAny[T](s, key); err == nil {
return val
}
return defaultVal
}
// Scan 方式获取缓存对象,适用于 Slice/Map/Struct 缓存对象, 反序列化扫描后放入引用地址变量
func (s *RedisCache) Scan(key string, refVal any) error {
return s.scanWithPC(getEntryPC(2, s, true), key, refVal)
}
func (s *RedisCache) scanWithPC(pc uintptr, key string, refVal any) error {
if key == "" {
return ErrKeyCanNotEmpty
}
if refVal == nil {
logWrapAttr(pc, slog.LevelError, "RedisCache Scan", slog.String("key", key), slog.String("error", ErrValIsNilPointer.Error()))
return fmt.Errorf("RedisCache: Scan key(%s) => refVal can not a nil pointer", key)
}
//直接获取 Set方式自动转换存入的Bytes
if valBytes, err := s._read(key); err != nil {
return fmt.Errorf("RedisCache: Scan key(%s) => key does not exist", key)
} else {
if err := getJsoner().Unmarshal(valBytes, refVal); err != nil {
logWrapAttr(pc, slog.LevelInfo, "RedisCache Scan Unmarshal", slog.String("key", key), slog.String("error", err.Error()))
return fmt.Errorf("RedisCache: Scan key(%s) => val does not Unmarshal, %s", key, err)
}
return nil
}
}
// Get returns the value for the given key, ie: ([]byte, nil).
// If the value does not exists it returns (nil, error)
func (s *RedisCache) Get(key string) ([]byte, error) {
v, err := s._read(key)
if err != nil {
if err != ErrKeyNotExist && err != ErrKeyCanNotEmpty {
logWrapAttr(getEntryPC(2, s, true), slog.LevelWarn, "RedisCache: Get",
slog.String("key", key),
slog.String("error", err.Error()))
}
return nil, err
}
return v, nil
}
// GetString
func (s *RedisCache) GetString(key string) string {
return _redisCacheReadDefault(s, key, "")
}
func (s *RedisCache) GetStringD(key string, defaultVal string) string {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetInt
func (s *RedisCache) GetInt(key string) int {
return _redisCacheReadDefault(s, key, 0)
}
func (s *RedisCache) GetIntD(key string, defaultVal int) int {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetInt8
func (s *RedisCache) GetInt8(key string) int8 {
return _redisCacheReadDefault[int8](s, key, 0)
}
func (s *RedisCache) GetInt8D(key string, defaultVal int8) int8 {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetInt16
func (s *RedisCache) GetInt16(key string) int16 {
return _redisCacheReadDefault[int16](s, key, 0)
}
func (s *RedisCache) GetInt16D(key string, defaultVal int16) int16 {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetInt32
func (s *RedisCache) GetInt32(key string) int32 {
return _redisCacheReadDefault[int32](s, key, 0)
}
func (s *RedisCache) GetInt32D(key string, defaultVal int32) int32 {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetInt64
func (s *RedisCache) GetInt64(key string) int64 {
return _redisCacheReadDefault[int64](s, key, 0)
}
func (s *RedisCache) GetInt64D(key string, defaultVal int64) int64 {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetUint
func (s *RedisCache) GetUint(key string) uint {
return _redisCacheReadDefault[uint](s, key, 0)
}
func (s *RedisCache) GetUintD(key string, defaultVal uint) uint {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetUint8
func (s *RedisCache) GetUint8(key string) uint8 {
return _redisCacheReadDefault[uint8](s, key, 0)
}
func (s *RedisCache) GetUint8D(key string, defaultVal uint8) uint8 {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetUint16
func (s *RedisCache) GetUint16(key string) uint16 {
return _redisCacheReadDefault[uint16](s, key, 0)
}
func (s *RedisCache) GetUint16D(key string, defaultVal uint16) uint16 {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetUint32
func (s *RedisCache) GetUint32(key string) uint32 {
return _redisCacheReadDefault[uint32](s, key, 0)
}
func (s *RedisCache) GetUint32D(key string, defaultVal uint32) uint32 {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetUint64
func (s *RedisCache) GetUint64(key string) uint64 {
return _redisCacheReadDefault[uint64](s, key, 0)
}
func (s *RedisCache) GetUint64D(key string, defaultVal uint64) uint64 {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetFloat32
func (s *RedisCache) GetFloat32(key string) float32 {
return _redisCacheReadDefault[float32](s, key, 0)
}
func (s *RedisCache) GetFloat32D(key string, defaultVal float32) float32 {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetFloat64
func (s *RedisCache) GetFloat64(key string) float64 {
return _redisCacheReadDefault[float64](s, key, 0)
}
func (s *RedisCache) GetFloat64D(key string, defaultVal float64) float64 {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetBool
func (s *RedisCache) GetBool(key string) bool {
return _redisCacheReadDefault(s, key, false)
}
func (s *RedisCache) GetBoolD(key string, defaultVal bool) bool {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetTime
func (s *RedisCache) GetTime(key string) time.Time {
return _redisCacheReadDefault(s, key, time.Time{})
}
func (s *RedisCache) GetTimeD(key string, defaultVal time.Time) time.Time {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetDuration time.Duration => int64
func (s *RedisCache) GetDuration(key string) time.Duration {
return _redisCacheReadDefault(s, key, time.Duration(0))
}
func (s *RedisCache) GetDurationD(key string, defaultVal time.Duration) time.Duration {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetTimeMonth time.Month => int
func (s *RedisCache) GetTimeMonth(key string) time.Month {
return _redisCacheReadDefault(s, key, time.Month(0))
}
// GetTimeMonthD
func (s *RedisCache) GetTimeMonthD(key string, defaultVal time.Month) time.Month {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetTimeWeekday
func (s *RedisCache) GetTimeWeekday(key string) time.Weekday {
return _redisCacheReadDefault(s, key, time.Weekday(0))
}
// GetTimeWeekdayD
func (s *RedisCache) GetTimeWeekdayD(key string, defaultVal time.Weekday) time.Weekday {
return _redisCacheReadDefault(s, key, defaultVal)
}
// GetIP net.IP(nil)
func (s *RedisCache) GetIP(key string) net.IP {
return _redisCacheReadDefault(s, key, net.IP(nil))
}
// GetIPDefault
func (s *RedisCache) GetIPD(key string, defaultVal net.IP) net.IP {
return _redisCacheReadDefault(s, key, defaultVal)
}
func (s *RedisCache) _calculateExpire(expire time.Duration) time.Duration {
if expire <= 0 {
return 0
} else if expire <= time.Second {
return time.Second
}
return expire
}
// Set 设置缓存
func (s *RedisCache) Set(key string, val any) error {
if key == "" {
return ErrKeyCanNotEmpty
}
if bytes, err := s._valMarshal(key, val); err != nil {
return err
} else {
if err := s.client.Set(s.ctx, key, bytes, s.defaultExpire).Err(); err != nil {
logWrapAttr(getEntryPC(3, s, true), slog.LevelError, "RedisCache: Set error", slog.String("key", key), slog.String("error", err.Error()))
return err
}
return nil
}
}
// SetExpire 设置缓存(标记过期时间)
func (s *RedisCache) SetExpire(key string, val any, expire time.Duration) error {
if key == "" {
return ErrKeyCanNotEmpty
}
if bytes, err := s._valMarshal(key, val); err != nil {
return err
} else {
if err := s.client.Set(s.ctx, key, bytes, s._calculateExpire(expire)).Err(); err != nil {
logWrapAttr(getEntryPC(3, s, true), slog.LevelError, "RedisCache: Set error", slog.String("key", key), slog.String("error", err.Error()))
return err
}
return nil
}
}
// SetNotExist 设置缓存(标记过期时间,为0表示永不过期),不存在则设置
func (s *RedisCache) SetNotExist(key string, val any, expire time.Duration) error {
if key == "" {
return ErrKeyCanNotEmpty
}
if bytes, err := s._valMarshal(key, val); err != nil {
return err
} else {
if err := s.client.SetNX(s.ctx, key, bytes, s._calculateExpire(expire)).Err(); err != nil {
logWrapAttr(getEntryPC(3, s, true), slog.LevelError, "RedisCache: Set error", slog.String("key", key), slog.String("error", err.Error()))
return err
}
return nil
}
}
func _redisCacheChange[T int | int32 | int64 | float32 | float64](s *RedisCache, key string, step T, expire time.Duration) (T, error) {
if key == "" {
return 0, ErrKeyCanNotEmpty
}
// 如果不存在则存储
if !s.Has(key) {
return step, s.SetExpire(key, step, expire)
}
// 如果存在则更新
val, err := _redisCacheReadAny[T](s, key)
if err != nil {
return val, err
}
if step == 0 {
return val, err
}
// 更新时保持原有的过期时间
ttl, err := s.client.TTL(s.ctx, key).Result()
if err != nil {
return val, err
}
val += step
if ttl > 0 {
return val, s.SetExpire(key, val, ttl)
}
return val, s.SetExpire(key, val, 0)
}
// ChangeInt 自增自减更新缓存(步进值为正数表示自增,步进值为负数表示自减)
// - expire 过期时间,如key存在则继续沿用原有过期时间,如key不存在则存储并设置过期时间
func (s *RedisCache) ChangeInt(key string, step int, expire time.Duration) (int, error) {
return _redisCacheChange(s, key, step, expire)
}
// ChangeInt32 自增自减更新缓存(步进值为正数表示自增,步进值为负数表示自减)
// - expire 过期时间,如key存在则继续沿用原有过期时间,如key不存在则存储并设置过期时间
func (s *RedisCache) ChangeInt32(key string, step int32, expire time.Duration) (int32, error) {
return _redisCacheChange(s, key, step, expire)
}
// ChangeInt64 自增自减更新缓存(步进值为正数表示自增,步进值为负数表示自减)
// - expire 过期时间,如key存在则继续沿用原有过期时间,如key不存在则存储并设置过期时间
func (s *RedisCache) ChangeInt64(key string, step int64, expire time.Duration) (int64, error) {
return _redisCacheChange(s, key, step, expire)
}
// ChangeFloat32 自增自减更新缓存(步进值为正数表示自增,步进值为负数表示自减)
// - expire 过期时间,如key存在则继续沿用原有过期时间,如key不存在则存储并设置过期时间
func (s *RedisCache) ChangeFloat32(key string, step float32, expire time.Duration) (float32, error) {
return _redisCacheChange(s, key, step, expire)
}
// ChangeFloat64 自增自减更新缓存(步进值为正数表示自增,步进值为负数表示自减)
// - expire 过期时间,如key存在则继续沿用原有过期时间,如key不存在则存储并设置过期时间
func (s *RedisCache) ChangeFloat64(key string, step float64, expire time.Duration) (float64, error) {
return _redisCacheChange(s, key, step, expire)
}
// Delete 删除缓存
func (s *RedisCache) Del(keys ...string) error {
if len(keys) > 0 {
// 如存在tag/key关系则同步删除
for _, key := range keys {
if key == "" {
continue
}
// 获取 key => tags 集合
if tags, err := s.tags.getKeyTags(key); err == nil {
for _, tag := range tags {
s.tags.delteteTagKey(tag, key)
}
// 最后删除 key => tags 集合
s.tags.delteteKeys(key)
}
}
return s.client.Del(s.ctx, keys...).Err()
}
return nil
}
// _delWithoutTag 删除缓存,不删除tag/key关系(内部redisTags回调)
func (s *RedisCache) _delWithoutTag(keys ...string) error {
if len(keys) > 0 {
return s.client.Del(s.ctx, keys...).Err()
}
return nil
}
// Clear 清空全部缓存
func (s *RedisCache) Clear() error {
return s.client.FlushDB(s.ctx).Err()
}
// Size 获取已缓存数据的条目
func (s *RedisCache) Size() int64 {
val, err := s.client.DBSize(s.ctx).Result()
if err != nil {
return 0
}
if val > 0 {
//这里需要排除 redisCacheTags 添加的Tags集合
var cursor uint64
for {
var keys []string
var err error
keys, cursor, err = s.client.Scan(s.ctx, cursor, redisTagSetPre+"*", 0).Result()
if err != nil {
break
}
val = val - int64(len(keys))
// 没有更多key了
if cursor == 0 {
break
}
}
}
return val
}
func (s *RedisCache) StoreType() StoreType {
return StoreRedis
}
// Stats 获取性能指标统计信息
func (s *RedisCache) Stats() StoreStats {
st := StoreStats{
StoreType: StoreRedis,
Entry: s.Size(),
Hit: atomic.LoadInt64(&s.hitCount),
Miss: atomic.LoadInt64(&s.missCount),
}
// 计算命中率(四位精度)
if st.Hit == 0 {
st.HitRate = 0
return st
}
st.HitRate = math.Round(float64(st.Hit)/float64(st.Hit+st.Miss)*10000) / 10000
return st
}
// 判断当且Cache是否为全局默认实例
func (s *RedisCache) IsGlobal() bool {
return s.isGlobal
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/gousing/cache.git
git@gitee.com:gousing/cache.git
gousing
cache
cache
v1.1.1

搜索帮助