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
}