kubernetes 中的使用的缓存 ---lru.Cache

lru.Cache

lru 源码位置 https://github.com/golang/groupcache/tree/master/lru
LRU(Least Recently Used)

  • 特点:即最近最少使用,优先移除最久未使用的数据,按时间维度衡量。
  • 数据结构:双向链表和HashMap
  • 淘汰原则:根据缓存数据使用的时间,将最不经常使用的缓存数据优先淘汰。如果一个数据在最近一段时间内都没有被访问,那么在将来他被访问的可能性也很小

先看一下Cache代码结构

type Cache struct {
	// MaxEntries is the maximum number of cache entries before
	// an item is evicted. Zero means no limit.
	MaxEntries int   // 缓存容量限制

	// OnEvicted optionally specifies a callback function to be
	// executed when an entry is purged from the cache.
	OnEvicted func(key Key, value interface{})  // 删除的时候可以指定一个回调函数

	ll    *list.List  // 以根节点为起点的一个环形双向链表
	cache map[interface{}]*list.Element   // 以map形式
}

存储的值的结构
在这里插入图片描述

type Key interface{}

type entry struct {
	key   Key
	value interface{}
}

初始化方法

func New(maxEntries int) *Cache {
	return &Cache{
		MaxEntries: maxEntries,  // 指定容量
		ll:         list.New(),
		cache:      make(map[interface{}]*list.Element),
	}
}

Cache.ll对象的初始化详解

// New returns an initialized list.
func New() *List { return new(List).Init() }

func (l *List) Init() *List {
	l.root.next = &l.root
	l.root.prev = &l.root
	l.len = 0
	return l
}
///
type List struct {
	root Element // sentinel list element, only &root, root.prev, and root.next are used
	len  int     // current list length excluding (this) sentinel element
}

可以先看List这个结构,这是go SDK自带的链表结构(“container/list”),而lru.Cache就是建立在List之上的。List是一个双向环形链表,每一个节点叫做Element。len表示List的长度
List初始化
Element

type Element struct {
	// Next and previous pointers in the doubly-linked list of elements.
	// To simplify the implementation, internally a list l is implemented
	// as a ring, such that &l.root is both the next element of the last
	// list element (l.Back()) and the previous element of the first list
	// element (l.Front()).
	next, prev *Element   // 前后对象的指针

	// The list to which this element belongs.
	list *List  // 所属的List

	// The value stored with this element.
	Value interface{}  // 这个元素对应的值
}

接下来我们逐一分析一下lru给我们提供的方法,从方法中就可以看出来lru的工作思路
Add方法

// Add adds a value to the cache.
func (c *Cache) Add(key Key, value interface{}) {
	if c.cache == nil {  // 如果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移动到list根节点后面
		ee.Value.(*entry).value = value
		return
	}
	ele := c.ll.PushFront(&entry{key, value}) // 把value插入到root的后面
	c.cache[key] = ele   // 添加到map里面
	if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {  // 链表长度超限
		c.RemoveOldest()
	}
}

这里面涉及到3个方法

  • c.ll.MoveToFront(ee) --调用List的方法,基于元素已经存在于cache,需要更新
  • c.ll.PushFront --调用List的方法,元素不在cache里面,需要add
  • c.RemoveOldest() --自身封装的方法
  1. c.ll.MoveToFront(ee)
func (l *List) MoveToFront(e *Element) {  // 元素移动到前面位置
	if e.list != l || l.root.next == e { // 这个元素不属于此List,或者此元素刚好已经在根节点下的第一个元素,则不需要移动此元素
		return
	}
	// see comment in List.Remove about initialization of l
	l.move(e, &l.root)  // 把这个元素移动到root下的第一个元素
}
// --------------------------------
func (l *List) move(e, at *Element) *Element {
	if e == at {  // 自身就是根,直接返回
		return e
	}
	e.prev.next = e.next 
	e.next.prev = e.prev

	e.prev = at
	e.next = at.next
	e.prev.next = e
	e.next.prev = e

	return e
}

move里面的代码我用一个简单的示意图表示一下会比较容易看明白
在这里插入图片描述
可以看到move动作是把一个元素移动到了根元素的下一位。
2. c.ll.PushFront PushFront添加一个元素到cache的过程

// PushFront inserts a new element e with value v at the front of list l and returns e.
func (l *List) PushFront(v interface{}) *Element {
	l.lazyInit()
	return l.insertValue(v, &l.root)
}

先看懒加载 lazyInit,如果root的下一个元素为nil,说明没有初始化,调用Init初始化,上面已经讲过初始化了

// lazyInit lazily initializes a zero List value.
func (l *List) lazyInit() {
	if l.root.next == nil {
		l.Init()
	}
}

下面是insertValue

func (l *List) insertValue(v interface{}, at *Element) *Element {
	return l.insert(&Element{Value: v}, at)
}
// insert inserts e after at, increments l.len, and returns e.
func (l *List) insert(e, at *Element) *Element {
	e.prev = at
	e.next = at.next
	e.prev.next = e
	e.next.prev = e
	e.list = l  // e元素属于这个list(l)
	l.len++  // 长度 +1
	return e
}

插入动作其实很简单
在这里插入图片描述
新添加的元素紧随根的后面
3. c.RemoveOldest() 删除最后一个元素

// RemoveOldest removes the oldest item from the cache.
func (c *Cache) RemoveOldest() {
	if c.cache == nil {
		return
	}
	ele := c.ll.Back()
	if ele != nil {
		c.removeElement(ele)
	}
}
// Back returns the last element of list l or nil if the list is empty.
func (l *List) Back() *Element {
	if l.len == 0 {
		return nil
	}
	return l.root.prev
}

c.ll.Back() 返回根的前一个元素,从上图的环形双向链表可以看到,root的prev就是从根顺序遍历的最后一个元素

func (c *Cache) removeElement(e *list.Element) {
	c.ll.Remove(e)
	kv := e.Value.(*entry)
	delete(c.cache, kv.key)
	if c.OnEvicted != nil {
		c.OnEvicted(kv.key, kv.value)
	}
}

先跳过c.ll.Remove(e),删除map里面的值,如果有清理的函数,就执行一下清理函数,下面再看c.ll.Remove(e)

func (l *List) Remove(e *Element) interface{} {
	if e.list == l { // 如果e属于list链表
		// if e.list == l, l must have been initialized when e was inserted
		// in l or l == nil (e is a zero Element) and l.remove will crash
		l.remove(e) // 移除e
	}
	return e.Value
}
// remove removes e from its list, decrements l.len, and returns e.
func (l *List) remove(e *Element) *Element {
	e.prev.next = e.next
	e.next.prev = e.prev
	e.next = nil // avoid memory leaks
	e.prev = nil // avoid memory leaks
	e.list = nil
	l.len--
	return e
}

操作环形链表的倒数第二个元素,下一个指向根root,根的上一个指向倒数第二个;然后在把e的属性清空;长度 -1
到这Add方法就完了,总结一下:来一个元素,先判断是否存在于cache中,如果存在,则把元素移动到根节点的下一个位置,更新元素对应的值;如果不存在,则把元素加入到cache中,放到根节点的下一个位置。接下来判断cache中的元素有没有超过容量限制,如果超过了,则把链表的最后一个元素删除掉;如果没有超过,结束。
Get()方法

// Get looks up a key's value from the cache.
func (c *Cache) Get(key Key) (value interface{}, ok bool) {
	if c.cache == nil {  // cache为空,直接返回
		return
	}
	if ele, hit := c.cache[key]; hit {
		c.ll.MoveToFront(ele)  // 前面讲过了,把ele移动到根节点的下一个位置
		return ele.Value.(*entry).value, true
	}
	return
}

Remove()方法

// Remove removes the provided key from the cache.
func (c *Cache) Remove(key Key) {
	if c.cache == nil {  // cache为空,直接返回
		return
	}
	if ele, hit := c.cache[key]; hit {  // 如果缓存中存在这个元素
		c.removeElement(ele)  // 移除掉这个元素,前面讲过了
	}
}

RemoveOldest()方法

func (c *Cache) RemoveOldest() {
	if c.cache == nil {  // cache为空,直接返回
		return
	}
	ele := c.ll.Back()  // 获取最后一个元素
	if ele != nil {
		c.removeElement(ele)  // 移除这个元素
	}
}

Len()

func (c *Cache) Len() int {
	if c.cache == nil {
		return 0
	}
	return c.ll.Len()  // 返回链表的长度
}

Clear()方法

// Clear purges all stored items from the cache.
func (c *Cache) Clear() {
	if c.OnEvicted != nil {
		for _, e := range c.cache {
			kv := e.Value.(*entry)
			c.OnEvicted(kv.key, kv.value)
		}
	}
	c.ll = nil
	c.cache = nil
}

这个前面没有讲到,但是也比较容易,如果没有定义清理的回调函数的话,直接把Cache的属性都置空就行,如果定义了回调函数,则先遍历所有的元素,执行回调函数调用,在清理Cache

总结:lru.Cache 不支持并发操作。cache提供元素hash存储,支持高效查询;ll是一个环形双向链表,来记录元素的操作顺序。从上面提供的方法可以看出,整个链表把所有的元素按照时间上操作顺序排序,最近操作的放在最前面,可以删除指定元素,也可以删除最后一个元素(最长时间为被使用)。更新,插入都使用Add方法完成。提供查询功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值