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}
}
许可证
更多关于golang高性能多层级缓存管理插件库go-cache的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于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()
}
性能优化建议
- 合理设置过期时间:根据数据更新频率设置适当的过期时间
- 批量操作:尽可能使用批量操作减少锁竞争
- 避免大对象:缓存过大的对象会影响性能
- 监控缓存命中率:实现简单的监控了解缓存效果
- 考虑多级缓存: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 是一个简单但功能强大的缓存库,适用于大多数需要内存缓存的场景。通过合理使用它的特性,可以显著提升应用程序的性能。