乐趣区

关于go:patrickmngocache源码阅读与分析

简介

go-cache 宽泛应用在 go 语言编程中,适宜迎来在单机上 存储键值对模式的内存缓存。
在 github 上地址为 https://github.com/patrickmn/go-cache
他在并发的时候,线程平安 (读写锁) + map[string]interface{} + 过期工夫 来作为 go 的本地化存储。
这也是他的三大个性:

  • 线程平安,通过读写锁反对多个协程并发拜访
  • 不须要序列化,键值对模式,任意值类型 map[string]interface{}
  • 自定义每个 key 的过期工夫

数据结构

次要有 Cache,以及其组成 cache,Item 两个构造。

type Cache struct {
    *cache
    // If this is confusing, see the comment at the bottom of New()
    // 如果这令人困惑,请参阅 New()底部的正文。}

type cache struct {
    defaultExpiration time.Duration
    items             map[string]Item  // 存储键值对
    mu                sync.RWMutex     // 读写锁,并发平安
    onEvicted         func(string, interface{})  // 被革除时的回调函数
    janitor           *janitor         // 脚本,定期清理过期数据
}

type Item struct {Object     interface{} // 存储的值
    Expiration int64   // 到期工夫
}

创立 Cache 对象

应用 New(defaultExpiration 默认过期工夫, cleanupInterval 定时清理工夫)函数来初始化。
传递到两个参数:key 的过期工夫,以及定时脚本清理过期数据的工夫。

// Return a new cache with a given default expiration duration and cleanup interval.
// 返回具备给定默认过期和革除工夫的 new cache 新缓存
// If the expiration duration is less than one (or NoExpiration),
// the items in the cache never expire (by default), 
// 如果 到期工夫为 -1, 永不过期
// and must be deleted manually.
// 并且只能手动删除。// If the cleanup interval is less than one, expired items are not
// 如果革除工夫小于 1,过期 item,在 call 之前是没有被删除的。// deleted from the cache before calling c.DeleteExpired().
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
    // 这个代码,确认是否启动 janitor 看门人 goroutine
    // 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.
    C := &Cache{c}
    if ci > 0 {runJanitor(c, ci)  // 调用守卫过程来清理过期数据
       runtime.SetFinalizer(C, stopJanitor)
       // runtime.SetFinalizer(C, stopJanitor)会指定调用函数进行后盾 goroutine,// 当 GC 筹备开释对象时,会调用 stopJanitor 办法,// Run 函数中 j.stop 通道会输入一个信号,从而退出协程。}
    return C
}

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.C 通道会输入一个值, 调用 DeleteExpired()函数
    // 该函数会通过遍历 cache 中的 map[string]Item 的过期工夫,过期则间接从 map 中删除,// 如果该值有回调函数,则在删除后执行回调函数。ticker := time.NewTicker(j.Interval)
    for {
       select {
       case <-ticker.C:
          c.DeleteExpired()
       case <-j.stop:
          ticker.Stop()
          return
       }
    }
}

// Delete all expired items from the cache.
// 删除 cache 中所有的过期 items
// 此时会加锁,如果定时清理的工夫比拟长,并且 key 比拟多的话,// 会导致始终被  清理协程锁住。其余的协程没法写入。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)
    }
}

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
}

Get 获取数据

// Get an item from the cache. Returns the item or nil, and a bool indicating
// whether the key was found.
// 从 cache 中 Get 一个 item. 返回 item 或者 nil,和一个 bool 值。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
    }
    // 获取到 item,是否能返回还须要判断过期工夫
    if item.Expiration > 0 {if time.Now().UnixNano() > item.Expiration {c.mu.RUnlock()
          return nil, false
       }
    }
    c.mu.RUnlock()
    return item.Object, true
}

Set 保留数据

set 保留数据,d 的表白有三种状况:

  • 为 0,应用默认的过期工夫
  • 为 -1,永不过期
  • 大于 0 的正常值,就是过期工夫
// Add an item to the cache, replacing any existing item. If the duration is 0
// 将 item 增加到缓存中,以更换任何现有 item。如果 duration 持续时间为 0
// (DefaultExpiration), the cache's default expiration time is used. If it is -1
// (DefaultExpiration),应用缓存的默认到期工夫。如果是 -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()}

常见问题

1、高并发

因为是加锁在整个 cache 上,相比那些加锁在分片上的其余缓存,并发会低一些。

2、对于内存溢出

如果设置的清理工夫为 0,就是永不清理,或者工夫过长,有可能导致缓存越来越多。
因为没有被动清理,占用的缓存越闹越大。

3、对于定时清理

如果工夫过长,一次清理太大,又因为加锁整个 cache,可能会导致其余的协程无奈写入。

4、对于 map[string]interface{}存储的值,有可能会变。

interface{},如果存的是数组,或者指针等,当取出应用的时候,批改值,会导致缓存中的原始值变动。

退出移动版