1.LRU
LRU(Least Recently Used,最近最久未使用算法)是一种常见的缓存淘汰算法,当缓存满时,淘汰最近最久未使用的元素,在很多分布式缓存系统(如Redis, Memcached)中都有广泛使用。其基本思想是如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当缓存满时,最久未被访问的数据最先被淘汰。具体做法是将最近使用的元素存放到靠近缓存顶部的位置,当一个新条目被访问时,LRU 将它放置到缓存的顶部。当缓存满时,较早之前访问的条目将从缓存底部被移除。
2.groupcache LRU Cache 简介
在 Go 中,如果想使用 LRU 缓存,可以使用 Google Golang 团队官方出品的开源库 groupcache ,开源地址见 Github.groupcache。LRU 缓存通过 groupcache/lru/lru.go实现,它主要是封装了一系列 LRU 缓存操作的相关的接口。主要有:
//创建一个 LRU Cache
func New(maxEntries int) *Cache
//向 Cache 中插入一个 KV
func (c *Cache) Add(key Key, value interface{})
//从 Cache 中获取一个 key 对应的 value
func (c *Cache) Get(key Key) (value interface{}, ok bool)
//从 Cache 中删除一个 key
func (c *Cache) Remove(key Key)
//从 Cache 中删除最久未被访问的数据
func (c *Cache) RemoveOldest()
//获取 Cache 中当前的元素个数
func (c *Cache) Len()
//清空 Cache
func (c *Cache) Clear()
注意,groupcache 中实现的 LRU Cache 并不是并发安全的,如果用于多个 Go 程并发的场景,需要加锁。
当然,除了使用 groupcache 的 LRU Cache,其他开源的库也可以参考一下,比如 HashiCorp 公司推出的 golang-lru。
3.源码剖析
LRU Cache 基于 map 与 list,map 用于快速检索,list 用于实现 LRU。具体实现如下:
package lru
import "container/list"
//Cache 是一个 LRU Cache,注意它并不是并发安全的
type Cache struct {
//MaxEntries 是 Cache 中实体的最大数量,0 表示没有限制
MaxEntries int
//OnEvicted 是一个可选的回调函数,当一个实体从 Cache 中被移除时执行
OnEvicted func(key Key, value interface{})
//ll是一个双向链表指针,执行一个 container/list 包中的双向链表
ll *list.List
//cache 是一个 map,存放具体的 k/v 对,value 是双向链表中的具体元素,也就是 *Element
cache map[interface{}]*list.Element
}
//key 是接口,可以是任意类型
type Key interface{}
//一个 entry 包含一个 key 和一个 value,都是任意类型
type entry struct {
key Key
value interface{}
}
//创建一个 LRU Cache。maxEntries 为 0 表示缓存没有大小限制
func New(maxEntries int) *Cache {
return &Cache{
MaxEntries: maxEntries,
ll: list.New(),
cache: make(map[interface{}]*list.Element),
}
}
//向 Cache 中插入一个 KV
func (c *Cache) Add(key Key, value interface{}) {
if c.cache == nil {
c.cache = make(map[interface{}]*list.Element)
c.ll = list.New()
}
if ee, ok := c.cache[key]; ok {
c.ll.MoveToFront(ee)
ee.Value.(*entry).value = value
return
}
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
c.RemoveOldest()
}
}
//传入一个 key,返回一个是否有该 key 以及对应 value
func (c *Cache) Get(key Key) (value interface{}, ok bool) {
if c.cache == nil {
return
}
if ele, hit := c.cache[key]; hit {
c.ll.MoveToFront(ele)
return ele.Value.(*entry).value, true
}
return
}
//从 Cache 中删除一个 KV
func (c *Cache) Remove(key Key) {
if c.cache == nil {
return
}
if ele, hit := c.cache[key]; hit {
c.removeElement(ele)
}
}
//从 Cache 中删除最久未被访问的数据
func (c *Cache) RemoveOldest() {
if c