golang高性能多层级缓存管理插件库go-cache的使用

golang高性能多层级缓存管理插件库go-cache的使用

go-cache简介

go-cache是一个灵活的Go语言多层级缓存库,可以与私有缓存(内存)共享缓存(如Redis)交互。它采用Cache-Aside策略处理两者,并通过Pub-Sub模式维护分布式系统间私有缓存的一致性。

缓存是提高系统性能和扩展性的常用技术,通过将频繁访问的数据临时复制到靠近应用的快速存储中实现。分布式应用通常使用以下策略缓存数据:

  • 私有缓存:数据保存在运行应用或服务的计算机本地
  • 共享缓存:作为可被多个进程和机器访问的公共源

使用本地私有缓存与共享缓存

主要特性

  • 易于使用:通过简单配置处理两种缓存机制,限制单个实例(pod)的内存大小
  • 保持一致性:通过Pub-Sub模式在分布式系统间驱逐键
  • 数据压缩:提供自定义的marshal和unmarshal函数
  • 修复并发问题:防止单个实例(pod)上发生数据竞争
  • 指标:提供回调函数测量性能(如命中率、私有缓存使用情况等)

数据流

使用Cache-Aside策略加载缓存

sequenceDiagram
    participant APP as Application
    participant M as go-cache
    participant L as Local Cache
    participant S as Shared Cache
    participant R as Resource (Microservice / DB)
    
    APP ->> M: Cache.Get() / Cache.MGet()
    alt Local Cache hit
        M ->> L: Adapter.MGet()
        L -->> M: {[]Value, error}
        M -->> APP: return
    else Local Cache miss but Shared Cache hit
        M ->> L: Adapter.MGet()
        L -->> M: cache miss
        M ->> S: Adapter.MGet()
        S -->> M: {[]Value, error}
        M ->> L: Adapter.MSet()
        M -->> APP: return
    else All miss
        M ->> L: Adapter.MGet()
        L -->> M: cache miss
        M ->> S: Adapter.MGet()
        S -->> M: cache miss
        M ->> R: OneTimeGetterFunc() / MGetterFunc()
        R -->> M: return from getter
        M ->> S: Adapter.MSet()
        M ->> L: Adapter.MSet()
        M -->> APP: return
    end

驱逐缓存

sequenceDiagram
    participant APP as Application
    participant M as go-cache
    participant L as Local Cache
    participant S as Shared Cache
    participant PS as PubSub
    
    APP ->> M: Cache.Del()
    M ->> S: Adapter.Del()
    S -->> M: return error if necessary
    M ->> L: Adapter.Del()
    L -->> M: return error if necessary
    M ->> PS: Pubsub.Pub() (broadcast key eviction)
    M -->> APP: return nil or error

安装

go get github.com/viney-shih/go-cache

使用示例

基本用法:设置和获取

采用Singleton模式,在main.go中初始化Factory,并将其传递给每个包或业务逻辑。

// Initialize the Factory in main.go
tinyLfu := cache.NewTinyLFU(10000)
rds := cache.NewRedis(redis.NewRing(&redis.RingOptions{
    Addrs: map[string]string{
        "server1": ":6379",
    },
}))

cacheFactory := cache.NewFactory(rds, tinyLfu)

像Redis一样作为常见的键值存储使用,但在内部协调多层缓存机制的使用。

type Object struct {
    Str string
    Num int
}

func Example_setAndGetPattern() {
    // 创建名为"set-and-get"的缓存组
    // 仅使用共享缓存,每个键将在10秒后过期
    c := cacheFactory.NewCache([]cache.Setting{
        {
            Prefix: "set-and-get",
            CacheAttributes: map[cache.Type]cache.Attribute{
                cache.SharedCacheType: {TTL: 10 * time.Second},
            },
        },
    })

    ctx := context.TODO()

    // 设置缓存
    obj := &Object{
        Str: "value1",
        Num: 1,
    }
    if err := c.Set(ctx, "set-and-get", "key", obj); err != nil {
        panic("not expected")
    }

    // 读取缓存
    container := &Object{}
    if err := c.Get(ctx, "set-and-get", "key", container); err != nil {
        panic("not expected")
    }
    fmt.Println(container) // 输出: Object{ Str: "value1", Num: 1}

    // 读取不存在的缓存
    if err := c.Get(ctx, "set-and-get", "no-such-key", container); err != nil {
        fmt.Println(err) // 输出: errors.New("cache key is missing")
    }

    // 输出:
    // &{value1 1}
    // cache key is missing
}

高级用法:Cache-Aside策略

GetByFunc()是实现getter函数参数的更简单方法。当缓存缺失时,它将自动重新填充缓存。

func ExampleCache_GetByFunc() {
    // 创建名为"get-by-func"的缓存组
    // 仅使用本地缓存,TTL为10分钟
    c := cacheFactory.NewCache([]cache.Setting{
        {
            Prefix: "get-by-func",
            CacheAttributes: map[cache.Type]cache.Attribute{
                cache.LocalCacheType: {TTL: 10 * time.Minute},
            },
            MarshalFunc:   msgpack.Marshal, // msgpack来自"github.com/vmihailenco/msgpack/v5"
            UnmarshalFunc: msgpack.Unmarshal,
        },
    })

    ctx := context.TODO()
    container2 := &Object{}
    if err := c.GetByFunc(ctx, "get-by-func", "key2", container2, func() (interface{}, error) {
        // getter用于在缓存缺失时生成数据,并自动重新填充缓存
        // 可以从DB或其他微服务读取
        // 假设我们根据键"key2"从MySQL读取,获取值Object{Str: "value2", Num: 2}
        return Object{Str: "value2", Num: 2}, nil
    }); err != nil {
        panic("not expected")
    }

    fmt.Println(container2) // Object{ Str: "value2", Num: 2}

    // 输出:
    // &{value2 2}
}

MGetter是另一种实现方式。在注册Setting时设置此函数。

func ExampleService_Create_mGetter() {
    // 创建名为"mgetter"的缓存组
    // 同时使用共享和本地缓存,TTL分别为1小时和10分钟
    c := cacheFactory.NewCache([]cache.Setting{
        {
            Prefix: "mgetter",
            CacheAttributes: map[cache.Type]cache.Attribute{
                cache.SharedCacheType: {TTL: time.Hour},
                cache.LocalCacheType:  {TTL: 10 * time.Minute},
            },
            MGetter: func(keys ...string) (interface{}, error) {
                // MGetter用于在缓存缺失时生成数据,并自动重新填充缓存
                // 可以从DB或其他微服务读取
                // 假设我们根据键"key3"从MySQL读取,获取值Object{Str: "value3", Num: 3}
                // 提示:记得返回切片,且项目顺序需要与参数中的键一致
                return []Object{{Str: "value3", Num: 3}}, nil
            },
            MarshalFunc:   cache.Marshal,
            UnmarshalFunc: cache.Unmarshal,
        },
    })

    ctx := context.TODO()
    container3 := &Object{}
    if err := c.Get(ctx, "mgetter", "key3", container3); err != nil {
        panic("not expected")
    }

    fmt.Println(container3) // Object{ Str: "value3", Num: 3}

    // 输出:
    // &{value3 3}
}

许可证

Apache-2.0


更多关于golang高性能多层级缓存管理插件库go-cache的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高性能多层级缓存管理插件库go-cache的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go-Cache 使用指南

go-cache 是 Golang 中一个简单而强大的内存缓存库,适用于构建高性能的多层级缓存系统。下面我将详细介绍它的使用方法和最佳实践。

基本使用

首先安装 go-cache 库:

go get github.com/patrickmn/go-cache

创建缓存实例

import (
	"fmt"
	"time"
	"github.com/patrickmn/go-cache"
)

func main() {
	// 创建一个默认过期时间为5分钟,每10分钟清理一次过期项的缓存
	c := cache.New(5*time.Minute, 10*time.Minute)
	
	// 或者创建没有过期时间的缓存
	// c := cache.New(cache.NoExpiration, 0)
}

基本操作

// 设置缓存项,使用默认过期时间
c.Set("key1", "value1", cache.DefaultExpiration)

// 设置永不过期的缓存项
c.Set("key2", "value2", cache.NoExpiration)

// 获取缓存项
if x, found := c.Get("key1"); found {
	fmt.Println("Found key1:", x)
}

// 删除缓存项
c.Delete("key1")

// 替换现有缓存项
c.Replace("key2", "new value", cache.DefaultExpiration)

// 获取并删除缓存项
if x, found := c.GetAndDelete("key2"); found {
	fmt.Println("Got and deleted:", x)
}

// 获取或设置缓存项(如果不存在)
value, found := c.Get("key3")
if !found {
	value = "default value"
	c.Set("key3", value, cache.DefaultExpiration)
}

高级特性

批量操作

// 批量设置
items := map[string]interface{}{
	"key4": "value4",
	"key5": "value5",
}
c.SetMultiple(items, cache.DefaultExpiration)

// 批量获取
keys := []string{"key4", "key5"}
results := c.GetMultiple(keys)
for k, v := range results {
	fmt.Printf("%s: %v\n", k, v)
}

带有回调的获取

value, err := c.GetWithLoader("key6", func() (interface{}, error) {
	// 当key6不存在时,执行这个回调函数生成值
	return "generated value", nil
}, cache.DefaultExpiration)
if err != nil {
	fmt.Println("Error:", err)
} else {
	fmt.Println("Got:", value)
}

缓存项计数

itemCount := c.ItemCount()
fmt.Println("Cache contains", itemCount, "items")

清空缓存

c.Flush()

实际应用示例

数据库查询缓存

func GetUserFromCacheOrDB(c *cache.Cache, userID string) (*User, error) {
	// 尝试从缓存获取
	if x, found := c.Get("user_" + userID); found {
		return x.(*User), nil
	}
	
	// 从数据库查询
	user, err := db.GetUser(userID)
	if err != nil {
		return nil, err
	}
	
	// 存入缓存,设置10分钟过期
	c.Set("user_+userID", user, 10*time.Minute)
	return user, nil
}

并发安全访问

func ConcurrentAccess(c *cache.Cache) {
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			key := fmt.Sprintf("key%d", i)
			c.Set(key, i, cache.DefaultExpiration)
			if x, found := c.Get(key); found {
				fmt.Printf("Goroutine %d got %v\n", i, x)
			}
		}(i)
	}
	wg.Wait()
}

性能优化建议

  1. 合理设置过期时间:根据数据更新频率设置适当的过期时间
  2. 批量操作:尽可能使用批量操作减少锁竞争
  3. 避免大对象:缓存过大的对象会影响性能
  4. 监控缓存命中率:实现简单的监控了解缓存效果
  5. 考虑多级缓存:go-cache 作为一级缓存,配合 Redis 等作为二级缓存

多级缓存实现示例

type MultiLevelCache struct {
	memoryCache *cache.Cache
	redisClient *redis.Client
}

func NewMultiLevelCache(redisAddr string) *MultiLevelCache {
	return &MultiLevelCache{
		memoryCache: cache.New(5*time.Minute, 10*time.Minute),
		redisClient: redis.NewClient(&redis.Options{Addr: redisAddr}),
	}
}

func (m *MultiLevelCache) Get(key string) (interface{}, error) {
	// 先查内存缓存
	if val, found := m.memoryCache.Get(key); found {
		return val, nil
	}
	
	// 内存没有则查Redis
	val, err := m.redisClient.Get(key).Result()
	if err == redis.Nil {
		return nil, fmt.Errorf("key not found")
	} else if err != nil {
		return nil, err
	}
	
	// 存入内存缓存
	m.memoryCache.Set(key, val, cache.DefaultExpiration)
	return val, nil
}

func (m *MultiLevelCache) Set(key string, value interface{}, expiration time.Duration) error {
	// 设置内存缓存
	m.memoryCache.Set(key, value, expiration)
	
	// 设置Redis缓存
	return m.redisClient.Set(key, value, expiration).Err()
}

go-cache 是一个简单但功能强大的缓存库,适用于大多数需要内存缓存的场景。通过合理使用它的特性,可以显著提升应用程序的性能。

回到顶部