patrickmn.gocache
一句话概括
基于内存的KV缓存,支持删除和过期以及持久化到文件并恢复。
使用示例
go.mod增加依赖
require github.com/patrickmn/go-cache v2.1.0+incompatible
package main import ( "log" "time" "github.com/patrickmn/go-cache" ) var c *cache.Cache func init() { // new 一个cache,设置默认过期时间5s和清理周期2s c = cache.New(5*time.Second, 2*time.Second) log.Println("init cache success") } func getByKey() { if value, ok := c.Get("abc"); ok { log.Println("has abc:", value) } else { log.Println("no abc") } } func main() { log.Println("use cache") getByKey() log.Println("set abc value") c.Set("abc", "def", cache.DefaultExpiration) getByKey() log.Println("sleep 6s") time.Sleep(6 * time.Second) getByKey() }
运行结果
2022/09/10 10:12:14 init cache success 2022/09/10 10:12:14 use cache 2022/09/10 10:12:14 no abc 2022/09/10 10:12:14 set abc value 2022/09/10 10:12:14 has abc: def 2022/09/10 10:12:14 sleep 6s 2022/09/10 10:12:20 no abc
源码分析
存储结构
type Item struct { // value Object interface{} // 添加key时间+过期时间 Expiration int64 } type Cache struct { *cache // If this is confusing, see the comment at the bottom of New() } type cache struct { defaultExpiration time.Duration items map[string]Item // 读写锁 mu sync.RWMutex // 删除key时的回调函数 onEvicted func(string, interface{}) // 定时清理过期的缓存 janitor *janitor } type janitor struct { // 清理过期缓存的时间间隔 Interval time.Duration // 是否停止清理 stop chan bool }
操作逻辑
func New(defaultExpiration, cleanupInterval time.Duration) *Cache { items := make(map[string]Item) return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) } func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache { c := newCache(de, m) // This trick ensures that the janitor goroutine (which--granted it // was enabled--is running DeleteExpired on c forever) does not keep // the returned C object from being garbage collected. When it is // garbage collected, the finalizer stops the janitor goroutine, after // which c can be collected. // 重点 // 因为回收cache时停止定时清理协程,确保下次回收cache时不会被定时清理协程阻止,所以包了一层 C := &Cache{c} if ci > 0 { runJanitor(c, ci) // C被GC回收时停止runJanitor中的协程 runtime.SetFinalizer(C, stopJanitor) } return C } func newCache(de time.Duration, m map[string]Item) *cache { if de == 0 { de = -1 } c := &cache{ defaultExpiration: de, items: m, } return c }
go对象可以通过SetFinalizer关联回调函数,当gc检测到unreachable对象有回调函数时,执行回调函数后取消关联。下一次gc时回收该对象。
func runJanitor(c *cache, ci time.Duration) { j := &janitor{ Interval: ci, stop: make(chan bool), } c.janitor = j go j.Run(c) } func (j *janitor) Run(c *cache) { ticker := time.NewTicker(j.Interval) for { select { case <-ticker.C: c.DeleteExpired() case <-j.stop: ticker.Stop() return } } } func stopJanitor(c *Cache) { c.janitor.stop <- true } func (c *cache) DeleteExpired() { var evictedItems []keyAndValue now := time.Now().UnixNano() c.mu.Lock() for k, v := range c.items { // "Inlining" of expired if v.Expiration > 0 && now > v.Expiration { ov, evicted := c.delete(k) if evicted { evictedItems = append(evictedItems, keyAndValue{k, ov}) } } } c.mu.Unlock() for _, v := range evictedItems { c.onEvicted(v.key, v.value) } } // Add an item to the cache, replacing any existing item. If the duration is 0 // (DefaultExpiration), the cache's default expiration time is used. If it is -1 // (NoExpiration), the item never expires. func (c *cache) Set(k string, x interface{}, d time.Duration) { // "Inlining" of set var e int64 if d == DefaultExpiration { d = c.defaultExpiration } if d > 0 { e = time.Now().Add(d).UnixNano() } c.mu.Lock() c.items[k] = Item{ Object: x, Expiration: e, } // TODO: Calls to mu.Unlock are currently not deferred because defer // adds ~200 ns (as of go1.) c.mu.Unlock() } // Get an item from the cache. Returns the item or nil, and a bool indicating // whether the key was found. func (c *cache) Get(k string) (interface{}, bool) { c.mu.RLock() // "Inlining" of get and Expired item, found := c.items[k] if !found { c.mu.RUnlock() return nil, false } if item.Expiration > 0 { if time.Now().UnixNano() > item.Expiration { c.mu.RUnlock() return nil, false } } c.mu.RUnlock() return item.Object, true } // Delete an item from the cache. Does nothing if the key is not in the cache. func (c *cache) Delete(k string) { c.mu.Lock() v, evicted := c.delete(k) c.mu.Unlock() if evicted { c.onEvicted(k, v) } } func (c *cache) delete(k string) (interface{}, bool) { if c.onEvicted != nil { if v, found := c.items[k]; found { delete(c.items, k) return v.Object, true } } delete(c.items, k) return nil, false }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具