go 语言实现简易内存缓存

本贴最后更新于 1642 天前,其中的信息可能已经事过景迁

hello,大家好,欢迎来到银之庭。我是 Z,一个普通的程序员。最近工作中遇到了个场景,一个 go 服务调用下游 redis 的 QPS 太高,导致 redis 报错比较多,所以决定在 go 服务里起个本地缓存,减少对 redis 的访问,上线后效果还不错,对 redis 的访问一下降了 90% 左右。今天我们就来看下怎么用 go 语言实现个本地内存缓存吧。

1. 明确目标

首先,我们要明确需要实现哪些特性。本地内存缓存最基本的是个 K-V 的存储,key 一般是 string,value 为了通用,定义成 interface{}。另外,还要有过期删除功能,避免一直读到本地的缓存,数据更新没有及时同步,这个过期时间通常由调用方传入。最后,考虑需不需要限制内存使用,在我实际的场景中,我是没有限制的,因为我缓存的内容其实很少,而且我设置的过期时间也很短,确定不会占用很大内存。总结一下就是:

  • 实现一个 K-V 存储,key 是 string,value 是 interface{}
  • 支持指定 key 的过期时间,内部实现过期删除
  • 可选实现限制内存使用

2. 代码实现

talk is cheap,下面直接贴完整代码,注释比较详细,相信大家都能看懂:

package main import ( "fmt" "sync" "time" ) // 缓存对象 type CacheItem struct { Value interface{} // 实际缓存的对象 LifeTime time.Duration // 存活时间,上游传入 CreatedAt time.Time // 创建时间,和存活时间一起决定是否过期 } // 缓存是否过期 func (item *CacheItem) Expired() bool { return time.Now().Sub(item.CreatedAt) > item.LifeTime } // 本地缓存实现类 type LocalCache struct { sync.RWMutex //继承读写锁,用于并发控制 Items map[string]*CacheItem // K-V存储 GCDuration int // 惰性删除,后台运行时间间隔,单位秒 } // 新建本地缓存 func NewLocalCache(gcDuration int) *LocalCache { localCache := &LocalCache{Items: map[string]*CacheItem{}, GCDuration: gcDuration} // 启动协程,定期扫描过期键,进行删除 go localCache.GC() return localCache } // 存入对象 func (cache *LocalCache) Put(key string, value interface{}, lifeTime time.Duration) { cache.Lock() defer cache.Unlock() cache.Items[key] = &CacheItem{ Value: value, LifeTime: lifeTime, CreatedAt: time.Now(), } } // 查询对象 func (cache *LocalCache) Get(key string) interface{} { cache.RLock() defer cache.RUnlock() if item, ok := cache.Items[key]; ok { if !item.Expired() { return item } else { // 键已过期,直接删除 // 需要注意的是,这里不能调用cache.Del()方法,因为go的读写锁是不支持锁升级的,会发生死锁 delete(cache.Items, key) } } return nil } // 删除缓存 func (cache *LocalCache) Del(key string) { cache.Lock() defer cache.Unlock() if _, ok := cache.Items[key]; ok { delete(cache.Items, key) } } // 异步执行,扫描过期键并删除 func (cache *LocalCache) GC() { for { select { case <-time.After(time.Duration(cache.GCDuration) * time.Second): keysToExpire := []string{} cache.RLock() for key, item := range cache.Items { if item.Expired() { keysToExpire = append(keysToExpire, key) } } cache.RUnlock() for _, keyToExpire := range keysToExpire { cache.Del(keyToExpire) } } } }

以上就是 go 语言里一个简单的本地缓存的实现了,如果大家只是轻度使用,不重度依赖本地缓存的话,直接自己手写一遍就行了,如果是重度依赖的话,建议还是找个开源的比较完善的实现,比如下面我要推荐的 go-cache 模块。

3. 开源实现:go-cache

点击查看源码:github 地址

它的实现原理和上面的差不多,只是考虑了更多细节,比如不使用 defer,来提升性能,处理 gc 的协程监听了一个关闭管道,使得我们可以从外部停止 gc 协程,以及注册 finalizer 函数,保证可以优雅关闭 gc 协程,并提供了更多有用的 API,代码结构也更规范,合理,推荐大家使用。

  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    499 引用 • 1395 回帖 • 245 关注
  • 缓存
    42 引用 • 70 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...