mlcache

package module
v0.0.0-...-25f5360 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 20, 2025 License: Apache-2.0 Imports: 15 Imported by: 0

README

gocore-mlcache

Fast and automated layered caching for Golang.

This library can be manipulated as a key/value store caching golang types , combining the power of the fastcache and redis, which results in an extremely performant and flexible caching solution.

Features:

  • Caching with TTLs.
  • Use singleflight to prevent dog-pile effects to your database/backend on cache misses.
  • Support prometheus metrics

Illustration of the various caching levels built into this library:

┌─────────────────────────────────────────────────┐
│ golang                                          │
│       ┌───────────────────────────────────────┐ │
│       │                                       │ │
│ L1    │                                       │ │
│       │             memory cache              │ │
│       └───────────────────────────────────────┘ │
│             │             │             │       │
│             ▼             ▼             ▼       │
│       ┌───────────────────────────────────────┐ │
│       │                                       │ │
│ L2    │              redis cache              │ │
│       │                                       │ │
│       └───────────────────────────────────────┘ │
│                           │ singleflight        │
│                           ▼                     │
│                  ┌──────────────────┐           │
│                  │     callback     │           │
│                  └────────┬─────────┘           │
└───────────────────────────┼─────────────────────┘
                            │
  L3                        │   I/O fetch
                            ▼

                   Database, API, DNS, Disk, any I/O...

The cache level hierarchy is:

  • L1: Least-Recently-Used golang memory cache using fastcache
  • L2: Redis cache. This level is only accessed if L1 was a miss, and prevents workers from requesting the L3 cache.
  • L3: A custom function that will only be run by a single goroutine to avoid the dog-pile effect on your database/backend (via singleflight). Values fetched via L3 will be set to the L2 cache for other goroutine to retrieve.

Usage

NewSimpleReader
package lcache

import (
  "context"
  "fmt"

  "xxx/dal/cache"
  "xxx/dal/devicedb"
  "xxx/dal/devicedb/model"
  "github.com/sunmi-OS/gocore-contrib/mlcache"
)

const DeviceInfoCachePrefix = "di"

func main() {
  var deviceInfoClient *redis.Client
  // init deviceInfoClient
  deviceInfoML := mlcache.NewSimpleReader(deviceInfoClient, func(ctx context.Context, key string) (interface{}, error) {
    return devicedb.GetDeviceInfoByID(ctx, key), nil
  }, mlcache.SimpleOpt{
    NotFoundFunc:   IsErrNotFound, // 除了gorm.ErrRecordNotFound外,若有其他错误需要跳过重试,定义在这里
    CacheKeyPrefix: DeviceInfoCachePrefix,       // 如果定义了CacheKeyPrefix,那么redis存储时key就会加上前缀,变成 CacheKeyPrefix:key
  })
  info := &model.IotDevice{}
  ctx := context.Background()
  status, err := deviceInfoML.Get(ctx, "device_id", info)
  fmt.Printf("%+v\n", status)
  fmt.Println(err)
  fmt.Printf("%+v\n", info)
}

NewMemoryDBReader
import(
  "github.com/sunmi-OS/gocore-contrib/mlcache"
  ristrettoStore "github.com/eko/gocache/store/ristretto/v4"
)

var deviceInfoML *mlcache.MemoryDBReader

func GetDeviceInfo(ctx context.Context, id int64) (*model.DeviceInfo, error) {
	data := &model.DeviceInfo{}
	_, err := deviceInfoML.Get(ctx, cast.ToString(vid), data)
	return data, err
}

func main() {
  deviceInfoSmallRCache, err := ristretto.NewCache(&ristretto.Config{
      NumCounters: 2e5,       // 需要是持久化数量的10倍,这里设置为20w
      MaxCost:     100 << 20, // 存储字节数
      BufferItems: 64,
  })
  if err != nil {
      panic(err)
  }
  deviceInfoSmallRStore = ristrettoStore.NewRistretto(deviceInfoSmallRCache)
  deviceInfoML = mlcache.NewMemoryDBReader(deviceInfoSmallRStore, func(ctx context.Context, key string) (val interface{}, err error) {
      return GetDeviceInfoByHttp(ctx, cast.ToInt64(key))
  }, mlcache.SimpleOpt{
      CacheKeyPrefix: vidInfoPrefix,
      Opt:            mlcache.Opt{TTL: deviceInfoExpireTTL},
  })

  // 数据预热,提前加载
  datas, err := GetManyDeviceInfos(ctx, deviceIds)
  if err != nil {
      return
  }
  for _, data := range datas {
      _ = deviceInfoML.Set(ctx, cast.ToString(data.Vid), data)
  }
}

TODO

  • 数据统计:缓存命中率、缓存回源率、读写速度、读写qps
  • 支持内存缓存

Documentation

Index

Constants

View Source
const (
	LevelL1 = "L1"
	LevelL2 = "L2"
	LevelL3 = "L3"
)
View Source
const DefaultRetryTime = 3

Variables

View Source
var (
	ErrInOutNotPointer     = ecode.NewV2(-1, "input and result must be pointers")
	ErrInOutNotSameType    = ecode.NewV2(-1, "input and result must have the same type")
	ErrOutFieldNotSettable = ecode.NewV2(-1, "result field is not settable")
)
View Source
var (
	ErrNotFound            = ecode.NewV2(-1, "not found")
	ErrSetHandleNotFound   = ecode.NewV2(-1, "set handler not found")
	ErrCleanHanderNotFound = ecode.NewV2(-1, "clean handler not found")
	ErrInputNotString      = ecode.NewV2(-1, "input is not string")
)
View Source
var (
	ErrInNotString = ecode.NewV2(-1, "input is not string")
)

Functions

func Copy

func Copy(src interface{}) interface{}

Copy creates a deep copy of whatever is passed to it and returns the copy in an interface{}. The returned value will need to be asserted to the correct type.

func CopyInterface

func CopyInterface(input interface{}, result interface{}) error

func CopyRecursive

func CopyRecursive(original, cpy reflect.Value)

CopyRecursive does the actual copying of the interface. It currently has limited support for what it can handle. Add as needed.

func DefaultDecoder

func DefaultDecoder(input interface{}, result interface{}) (err error)

func DefaultEncoder

func DefaultEncoder(input interface{}) (result interface{}, err error)

func DefaultTTL

func DefaultTTL(ttl *time.Duration) time.Duration

func GetCacheKey

func GetCacheKey(prefix string, key string) string

func GetPointer

func GetPointer(value interface{}) interface{}

func GetValue

func GetValue(pointer interface{}) interface{}

func Iface

func Iface(iface interface{}) interface{}

Iface is an alias to Copy; this exists for backwards compatibility reasons.

func ShakeTime10

func ShakeTime10(t time.Duration) time.Duration

ShakeTime10 +-10%的时间抖动

Types

type CacheStatus

type CacheStatus struct {

	// if found the key
	Found bool

	// CacheFlag: L1/L2/L3
	CacheFlag string
}

type CleanCacheHandler

type CleanCacheHandler func(ctx context.Context, key string) error

type Decoder

type Decoder func(str interface{}, val interface{}) error

type DecoderType

type DecoderType interface {
	~int32 | string | []byte
}

type Encoder

type Encoder func(val interface{}) (str interface{}, err error)

type GetCacheHandler

type GetCacheHandler func(ctx context.Context, key string) (val interface{}, found bool, err error)

type Interface

type Interface interface {
	DeepCopy() interface{}
}

Interface for delegating copy process to type

type LC

type LC struct {
	Name      string
	LevelName string
	// retry times when get/set handler failed
	Retry             int
	GetCacheHandler   GetCacheHandler
	SetCacheHandler   SetCacheHandler
	CleanCacheHandler CleanCacheHandler
	// encoder and decoder
	Encoder Encoder
	Decoder Decoder
}

func (*LC) Clean

func (lc *LC) Clean(ctx context.Context, key string) (err error)

func (*LC) Get

func (lc *LC) Get(ctx context.Context, key string, pointer interface{}) (cs CacheStatus, err error)

func (*LC) Set

func (lc *LC) Set(ctx context.Context, key string, val interface{}, ttl *time.Duration) (err error)

type MLCache

type MLCache interface {
	// TODO: support context
	Set(ctx context.Context, key string, val interface{}, opt ...Opt) error
	SetL2(ctx context.Context, key string, val interface{}, opt ...Opt) error
	Get(ctx context.Context, key string, val interface{}, opt ...Opt) (CacheStatus, error)
	Clean(ctx context.Context, key string) error
}

func New

func New(
	l2, l3 *LC,
) MLCache

type MemoryDBReader

type MemoryDBReader struct {
	// contains filtered or unexported fields
}

func NewMemoryDBReader

func NewMemoryDBReader(client *ristrettoStore.RistrettoStore, callback SimpleCallback, simpleOpt ...SimpleOpt) *MemoryDBReader

NewMemoryDBReader callback中val需要是指针类型,如&[]*struct{} 先读redis缓存,如果没有再读取callback,然后写入redis缓存 redis key存储格式是 cacheKeyPrefix:{key}

func (*MemoryDBReader) Delete

func (m *MemoryDBReader) Delete(ctx context.Context, key string) error

func (*MemoryDBReader) Get

func (m *MemoryDBReader) Get(ctx context.Context, key string, value interface{}, opt ...Opt) (CacheStatus, error)

func (*MemoryDBReader) Set

func (m *MemoryDBReader) Set(ctx context.Context, key string, value interface{}, opt ...Opt) error

type Opt

type Opt struct {
	// the ttl of key life cycle
	TTL time.Duration

	Timeout time.Duration
}

func DefaultOpt

func DefaultOpt(origin Opt, in ...Opt) Opt

type SetCacheHandler

type SetCacheHandler func(ctx context.Context, key string, val interface{}, ttl *time.Duration) error

type SimpleCallback

type SimpleCallback func(ctx context.Context, key string) (val interface{}, err error)

SimpleCallback 读mysql/mongodb等数据库 or 其他io

type SimpleOpt

type SimpleOpt struct {
	Opt            Opt
	NotFoundFunc   func(error) bool
	CacheKeyPrefix string
	Retry          int // 默认3次重试。1代表最多请求1次,2代表最多请求3次,以此类推。一般不需要调整,若<=0则会被调整为3。
}

type SimpleReader

type SimpleReader struct {
	// contains filtered or unexported fields
}

func NewSimpleReader

func NewSimpleReader(client *redis.Client, callback SimpleCallback, simpleOpt ...SimpleOpt) *SimpleReader

NewSimpleReader 需要传入指针类型,如&[]*struct{} 先读redis缓存,如果没有再读取callback,然后写入redis缓存 redis key存储格式是 cacheKeyPrefix:{key}

func (*SimpleReader) Delete

func (m *SimpleReader) Delete(ctx context.Context, key string) error

func (*SimpleReader) Get

func (m *SimpleReader) Get(ctx context.Context, key string, value interface{}, opt ...Opt) (CacheStatus, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL