modecache

package module
v1.0.4 Latest Latest
Warning

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

Go to latest
Published: Dec 26, 2025 License: MIT Imports: 15 Imported by: 0

README

ModeCache

English | 简体中文

Go Report Card GoDoc License

ModeCache is a universal cache encapsulation component designed to handle scenarios such as cache penetration and cache degradation, while reasonably controlling the timing of using limited resources like databases and cache.

Features

  • 🚀 High-performance cache access with cache penetration protection
  • 🛡️ Multiple cache strategies to meet different business scenario requirements
  • 🔌 Flexible plugin mechanism supporting custom extensions
  • 💾 Support for multiple storage backends (Redis, local cache)
  • 📊 Built-in monitoring metrics for easy observation of cache usage
  • 🧩 Generic support with type safety

Table of Contents

Installation

Install ModeCache using go get:

go get github.com/wheat-os/modecache

Quick Start

Basic Usage
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/wheat-os/modecache"
	"github.com/patrickmn/go-cache"
)

func main() {
	// Create local cache storage
	localCache := cache.New(5*time.Minute, 10*time.Minute)
	store := modecache.NewCacheStore(localCache)

	// Get data using default cache strategy
	ctx := context.Background()
	key := "user:123"

	userID, err := modecache.WrapWithTTL(ctx, store, key, 30*time.Second, func(ctx context.Context) (int, error) {
		// Simulate fetching data from database
		fmt.Println("Fetching from database...")
		return 123, nil
	})

	if err != nil {
		panic(err)
	}
	fmt.Printf("User ID: %d", userID)
}

Core Concepts

ModeCache consists of four core components:

1. Controller (CacheCtr)

The controller is mainly responsible for wrapping external access APIs, providing cache access protection, generic processing, and is responsible for providing the final external interface.

Note: The same set of cache resources should use the same cache controller.

2. Cache Strategy (Policy)

Cache strategy is used to describe cache access logic, determining when the program uses cache and when to use resources like databases.

3. Plugin

Plugins are extensions of cache strategies that can freely extend cache strategies, such as adding SRE circuit breaker mechanisms to cache strategies. The use of plugins behaves like interceptors, allowing custom operations to be performed before accessing cache or database.

4. Storage

Storage is used to store cache data, currently supporting two storage methods: Redis and local cache.

Cache Strategies

ModeCache provides multiple cache strategies suitable for different business scenarios:

1. EasyPloy

Simple strategy model that first tries to access cache, and if cache expires, tries to access database. If database access also fails, it returns an error.

// Create a simple strategy with 15 seconds expiration
policy := modecache.EasyPloy(15 * time.Second)

Applicable scenarios: Businesses that require strong consistency (globally consistent) and high timeliness (data is unusable after expiration), such as user information.

2. ReuseCachePloyIgnoreError

Reuse cache model that stores data in cache for a long time, uses business expiration time to control cache expiration, and uses cache data to complete service when database query fails.

// Create a reuse cache strategy with 30 seconds business expiration
policy := modecache.ReuseCachePloyIgnoreError(30 * time.Second)

Note: If cache is hit, when database query execution fails, this strategy will reuse cache data until the query execution succeeds.

3. FirstCachePolyIgnoreError

Fast cache model that saves cache for a long time, prioritizes cache usage, and uses business expiration time to control whether cache expires. If cache expires, it will start a singleton coroutine to access database asynchronously to refresh cache and return the cache data obtained this time.

// Create a fast cache strategy with 1 minute business expiration
policy := modecache.FirstCachePolyIgnoreError(1 * time.Minute)

Note: If cache is hit, when database query execution fails, this strategy will reuse cache data until the query execution succeeds.

Applicable scenarios: Scenarios with very high access frequency and no special requirements for timeliness, such as background business configuration information.

Storage

Redis Storage

Storage implemented based on Redis, supporting distributed cache.

// Create Redis storage
store := modecache.NewRedisStore(redisClient)
Redis Hash Storage

Storage implemented based on Redis Hash, suitable for scenarios that need to organize related data together.

// Create Redis Hash storage
ctx, store := modecache.NewRedisHashStore(context.Background(), redisClient, "redisKey", "hashKey")
Local Cache Storage

Local cache storage based on memory, with higher performance but not supporting distributed.

// Create local cache storage
store := modecache.NewCacheStore(cacheInstance)

Plugin System

ModeCache provides a flexible plugin mechanism that allows custom logic to be executed before and after cache access and database queries.

Plugin Interface
type Plugin interface {
	// InterceptCallQuery Intercept call before querying database
	// return: LoadingForQuery: If not empty, replace the executed LoadingForQuery
	// return: bool: Whether to allow continued plugin execution or early circuit breaking
	// return: error: Error will cause the process to end and return error
	InterceptCallQuery(ctx context.Context, key string, loadQuery LoadingForQuery) (LoadingForQuery, bool, error)

	// InterceptCallCache Intercept call before querying cache
	// return: LoadingForCache: If not empty, replace the executed LoadingForCache
	// return: bool: Whether to allow continued plugin execution or early circuit breaking
	// return: error: Error will cause the process to end and return error
	InterceptCallCache(ctx context.Context, key string, loadCache LoadingForCache) (LoadingForCache, bool, error)
}

Best Practices

Choosing Appropriate Cache Strategy and Storage

When choosing cache strategy and storage, consider the following metrics:

  1. Consistency
  2. Access performance
  3. Fault tolerance and degradation
  4. Timeliness

The choice of storage (shared cache Redis or local cache Cache) determines consistency and performance, while the choice of cache strategy determines timeliness and fault tolerance.

Scenario 1: User Information Cache

For businesses that require strong consistency (globally consistent) and high timeliness (data is unusable after expiration), such as user information:

// Use EasyPloy strategy and Redis storage
ctr := modecache.NewCacheController[User]("user-service", redisStore,
    modecache.WithPolicy[User](modecache.EasyPloy(5*time.Minute)),
)
Scenario 2: Configuration Information Cache

For scenarios with very high access frequency and no special requirements for timeliness, such as background business configuration information:

// Use FirstCachePolyIgnoreError strategy and local cache storage
ctr := modecache.NewCacheController[Config]("config-service", localCacheStore,
    modecache.WithPolicy[Config](modecache.FirstCachePolyIgnoreError(30*time.Minute)),
)

API Documentation

For detailed API documentation, please refer to GoDoc.

Main Types and Functions
  • CacheCtr[T]: Cache controller for managing cache access of specific types.
  • Store: Storage interface defining basic cache operations.
  • Policy: Cache strategy type defining cache access logic.
  • Plugin: Plugin interface allowing custom extension of cache behavior.
  • NewCacheController[T]: Create new cache controller.
  • NewRedisStore: Create Redis storage.
  • NewRedisHashStore: Create Redis Hash storage.
  • NewCacheStore: Create local cache storage.
  • EasyPloy: Create simple strategy model.
  • ReuseCachePloyIgnoreError: Create reuse cache strategy model.
  • FirstCachePolyIgnoreError: Create fast cache strategy model.

Contributing

Contributions are welcome! Please ensure:

  1. Follow the project's code style
  2. Add appropriate tests
  3. Update documentation

Before submitting a Pull Request, please ensure all tests pass:

go test ./...

License

ModeCache is licensed under the MIT License. See the LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	SHadowKeyPrefix = "shadow:"

	KeepTTL = -1 // 永久存储
)
View Source
const Mutex128Shards = 128

Variables

View Source
var (
	ErrKeyNonExistent  = errors.New("modecache: key does not exist")    // ErrKeyNonExistent 缓存键不存在。
	ErrUnpackingFailed = errors.New("modecache: warp unpacking failed") // warp 拆箱失败。
	ErrNil             = errors.New("null pointer")                     // Nil 空指针。
)

Functions

func DeleteStore added in v1.0.2

func DeleteStore(ctx context.Context, store Store, key string) error

DeleteStore 删除缓存

func GO

func GO(fn func())

func GetStore added in v1.0.1

func GetStore[T any](ctx context.Context, store Store, key string) (T, int, error)

GetStore 获取缓存

func SetStore added in v1.0.1

func SetStore[T any](ctx context.Context, store Store, key string, value T, ttl time.Duration) error

SetStore 设置缓存

func Wrap deprecated

func Wrap[T any](ctx context.Context, name string, store Store, key string, query Query[T]) (T, error)

Deprecated: use WrapWithTTL Wrap 控制器封装方法,创建默认的控制器, 注意 name 只能够对应一个缓存 T 如果,冲突创建,会引发错误 该方法默认使用 PolicyWarp 策略,应该使用 NewCacheController 来创建自定义的缓存控制器 使用缓存策略 EasyPloy(15 * time.Second)

func WrapForFirstIgnoreError deprecated

func WrapForFirstIgnoreError[T any](ctx context.Context, name string, store Store, key string, query Query[T]) (T, error)

Deprecated: use WrapForReuseIgnoreErrorWithTTL WrapForFirst 优先缓存封装模型, 注意 name 只能够对应一个缓存 T 如果,冲突创建,会引发错误 使用缓存策略 FirstCachePoly(1 * time.Minute) # 注意如果命中缓存,那么当 query 执行失败时,这个策略会重复使用缓存数据,直到 query 执行成功为止。

func WrapForFirstIgnoreErrorWithTTL

func WrapForFirstIgnoreErrorWithTTL[T any](ctx context.Context, store Store, key string, ttl time.Duration, query Query[T]) (T, error)

WrapForFirstIgnoreErrorWithTTL # 注意如果命中缓存,那么当 query 执行失败时,这个策略会重复使用缓存数据,直到 query 执行成功为止。

func WrapForReuseIgnoreError

func WrapForReuseIgnoreError[T any](ctx context.Context, name string, store Store, key string, query Query[T]) (T, error)

// Deprecated: use WrapForReuseIgnoreErrorWithTTL WrapForReuseIgnoreError 重用缓存封装模型, 注意 name 只能够对应一个缓存 T 如果,冲突创建,会引发错误 使用缓存策略 ReuseCachePloy(30 * time.Second) # 注意如果命中缓存,那么当 query 执行失败时,这个策略会重复使用缓存数据,直到 query 执行成功为止。

func WrapForReuseIgnoreErrorWithTTL

func WrapForReuseIgnoreErrorWithTTL[T any](ctx context.Context, store Store, key string, ttl time.Duration, query Query[T]) (T, error)

WrapForReuseIgnoreErrorWithTTL # 注意如果命中缓存,那么当 query 执行失败时,这个策略会重复使用缓存数据,直到 query 执行成功为止。

func WrapWithTTL

func WrapWithTTL[T any](ctx context.Context, store Store, key string, ttl time.Duration, query Query[T]) (T, error)

WrapWithTTL 简单的缓存策略,当 query 执行失败时,直接返回错误。

Types

type AbcBox

type AbcBox[T any] struct {
	Timestamp int `json:"Timestamp"`
	T         T   `json:"T"`
}

AbcBox 抽象箱

type CacheCtr

type CacheCtr[T any] struct {
	Name string // 缓存控制名称
	// contains filtered or unexported fields
}

func NewCacheController

func NewCacheController[T any](name string, store Store, optionChain ...Option[T]) *CacheCtr[T]

NewCacheController 创建一个缓存控制器, 默认使用简单策略模式,设置 15 秒的缓存过期时间

func (*CacheCtr[T]) GetStore

func (c *CacheCtr[T]) GetStore(ctx context.Context, key string) (T, int, error)

GetStore 从 Store 中获取缓存

func (*CacheCtr[T]) SetStore

func (c *CacheCtr[T]) SetStore(ctx context.Context, key string, value T, ttl time.Duration) error

SetStore 设置缓存到 Store

func (*CacheCtr[T]) Wrap

func (c *CacheCtr[T]) Wrap(ctx context.Context, key string, query Query[T]) (p T, err error)

Wrap 控制器的包装方法,控制使用 warp 方案

type CtxStorageKey

type CtxStorageKey struct{}

CtxStorageKey 上下文存储键,用来存储可变的 storage 实现替换全局 storage

type LimitQueryPlugin

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

使用 go 限流器实现 query 访问限流插件

func (*LimitQueryPlugin) InterceptCallCache

func (m *LimitQueryPlugin) InterceptCallCache(ctx context.Context, key string, loadCache LoadingForCache) (LoadingForCache, bool, error)

func (*LimitQueryPlugin) InterceptCallQuery

func (m *LimitQueryPlugin) InterceptCallQuery(ctx context.Context, key string, loadQuery LoadingForQuery) (LoadingForQuery, bool, error)

DB 限流器

type LoadingForCache

type LoadingForCache func(ctx context.Context, key string) (any, int, error)

LoadingForCache 封装查询方法,return:数据, 数据创建时间,错误

type LoadingForQuery

type LoadingForQuery func(ctx context.Context, key string, ttl time.Duration) (any, error)

LoadingForQuery 数据库封装方法

type MetricsPlugin

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

MetricsPlugin 指标插件

func (*MetricsPlugin) InterceptCallCache

func (m *MetricsPlugin) InterceptCallCache(ctx context.Context, key string, loadCache LoadingForCache) (LoadingForCache, bool, error)

func (*MetricsPlugin) InterceptCallQuery

func (m *MetricsPlugin) InterceptCallQuery(ctx context.Context, key string, loadQuery LoadingForQuery) (LoadingForQuery, bool, error)

type Mutex128

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

func (*Mutex128) Lock

func (rw *Mutex128) Lock(shard uint)

Lock locks rw for writing. If the lock is already locked for reading or writing, then Lock blocks until the lock is available.

func (*Mutex128) TryLock

func (rw *Mutex128) TryLock(shard uint) bool

func (*Mutex128) Unlock

func (rw *Mutex128) Unlock(shard uint)

Unlock unlocks rw for writing. It is a run-time error if rw is not locked for writing on entry to Unlock.

type Option

type Option[T any] func(m *CacheCtr[T])

func WithPlugins

func WithPlugins[T any](p ...Plugin) Option[T]

WithAddPlugin 设置想要使用的缓存插件

func WithPolicy

func WithPolicy[T any](p Policy) Option[T]

WithPolicy 设置需要使用的缓存策略

type Plugin

type Plugin interface {
	// InterceptCallQuery 查询 query 前拦截调用
	// return: LoadingForQuery: 不为空的场景,替换执行的 LoadingForQuery
	// return: bool:是否允许继续执行插件,还是提前熔断
	// return: error: 错误, 会导流程结束返回 error
	InterceptCallQuery(ctx context.Context, key string, loadQuery LoadingForQuery) (LoadingForQuery, bool, error)

	// InterceptCallCache 查询 cache 前拦截调用
	// return: LoadingForCache: 不为空的场景,替换执行的 LoadingForCache
	// return: bool:是否允许继续执行插件,还是提前熔断
	// return: error: 错误, 会导流程结束返回 error
	InterceptCallCache(ctx context.Context, key string, loadCache LoadingForCache) (LoadingForCache, bool, error)
}

访问控制插件

func NewLimitQueryPlugin

func NewLimitQueryPlugin(r rate.Limit, b int) Plugin

func NewMetricsPlugin

func NewMetricsPlugin(name string) Plugin

type Policy

type Policy func(ctx context.Context, key string, queryFormDB LoadingForQuery, queryFormCache LoadingForCache) (any, error)

Policy 缓存控制策略, 用来控制缓存策略

func EasyPloy

func EasyPloy(ttl time.Duration) Policy

EasyPloy 创建简单策略模型 该模式会先尝试访问缓存,如果缓存发生过期则尝试访问数据库,如果数据库也获取失败则返回错误。

func FirstCachePolyIgnoreError

func FirstCachePolyIgnoreError(expireTime time.Duration) Policy

FirstCachePolyIgnoreError 创建一个快速缓存模型 快速缓存模型,会长时间保存缓存,并且优先使用缓存,使用业务过期时间 expireTime 来控制缓存是否过期,如果缓存过期会 拉起一个单例携程来访问 query 异步刷新缓存,并且返回本次获取到的缓存中的数据,如果访问缓存失败,则退化为简单缓存模型 # 注意如果命中缓存,那么当 query 执行失败时,这个策略会重复使用缓存数据,直到 query 执行成功为止。

func ReuseCachePloyIgnoreError

func ReuseCachePloyIgnoreError(expireTime time.Duration) Policy

ReuseCachePloyIgnoreError 创建一个使用重用缓存的访问模式 重用缓存模型,会把数据长时间的存储到缓存中,使用业务过期时间 expireTime 来控制缓存的过期, 并且在 下游 query 接口无法调用成功的场景,使用缓存数据完成服务 # 注意如果命中缓存,那么当 query 执行失败时,这个策略会重复使用缓存数据,直到 query 执行成功为止。

type Query

type Query[T any] func(context.Context) (T, error)

Query 查询方法类型。

type RedisHashStore

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

NewRedisHashStore 创建 redis hash cache 注意 NewHashStore 设置过期时间会对整个 hash 进行设置

func NewRedisHashStore

func NewRedisHashStore(ctx context.Context, rd *redis.Client, rdsKey string, rdsHashKey string) (context.Context, *RedisHashStore)

NewRedisHashStoreWithPrefix 新创建 hashKey redis 其中 key: redis key, 最后存储的 redis key 注意这里不应该使用 modecache_key hashKey: redis hash key,注意不是 redis key return: ctx 需要向下传递用来替换默认的 storage

func (*RedisHashStore) Del

func (r *RedisHashStore) Del(ctx context.Context, _ string) error

func (*RedisHashStore) DelAll

func (r *RedisHashStore) DelAll(ctx context.Context) error

DelAll 删除整个 hash

func (*RedisHashStore) Get

func (r *RedisHashStore) Get(ctx context.Context, _ string) (any, error)

Get 获取缓存, 使用外部给定的 rds key 作为存储 key,避免和 modecache_key 冲突

func (*RedisHashStore) IsDirectStore

func (r *RedisHashStore) IsDirectStore() bool

IsDirectStore 判断是否是直接存储

func (*RedisHashStore) Set

func (r *RedisHashStore) Set(ctx context.Context, _ string, data any, ttl time.Duration) error

Set 设置缓存。

type SingleflightGroup

type SingleflightGroup struct {
	singleflight.Group
}

func (*SingleflightGroup) Do

func (s *SingleflightGroup) Do(ctx context.Context, key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool)

Do 影子链路支持

type Store

type Store interface {
	// Get 获取缓存。当缓存键不存在时返回 ErrKeyNonExistent 错误。
	Get(ctx context.Context, key string) (any, error)
	// Set 设置缓存, ttl 使用 KeepTTL 表示用不过期
	Set(ctx context.Context, key string, data any, ttl time.Duration) error
	// Del 删除缓存。
	Del(ctx context.Context, key string) error

	// IsDirectStore 释放可以直接存储数据,而不需要编码后存储
	// 当 IsDirectStore 为 True 时,存储管理器会少一次编码和解码的操作,以提高缓存读取的性能(本地缓存可用)
	IsDirectStore() bool
}

func NewCacheStore

func NewCacheStore(c *cache.Cache) Store

func NewRedisStore

func NewRedisStore(rd *redis.Client) Store

NewRedisCache 新创建应该 redis cache

type TaskResult

type TaskResult[T any] struct {
	Key string        // 缓存 Key
	T   T             // 缓存内容
	TTL time.Duration // 缓存的过期时间, KeepTTL 永久存储
}

type TimerJobList

type TimerJobList[T any] func(ctx context.Context) ([]*TaskResult[T], error)

Jump to

Keyboard shortcuts

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