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
}

 

posted on 2023-07-15 10:15  王景迁  阅读(43)  评论(0编辑  收藏  举报

导航