• Golang groupcache LRU 缓存简介与用法


    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.cache == nil {
    		return
    	}
    	ele := c.ll.Back()
    	if ele != nil {
    		c.removeElement(ele)
    	}
    }
    
    //从 Cache 中删除一个元素,供内部调用
    func (c *Cache) removeElement(e *list.Element) {
    	//先从 list 中删除
    	c.ll.Remove(e)
    	
    	kv := e.Value.(*entry)
    
    	//再从 map 中删除
    	delete(c.cache, kv.key)
    	
    	//如果回调函数不为空则调用
    	if c.OnEvicted != nil {
    		c.OnEvicted(kv.key, kv.value)
    	}
    }
    
    //获取 Cache 当前的元素个数
    func (c *Cache) Len() int {
    	if c.cache == nil {
    		return 0
    	}
    	return c.ll.Len()
    }
    
    //清空 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
    }

    4.使用示例

    从上面的源码分析来看,groupcache 实现的 LRU Cache 还是比较简单的,Google 一直秉持着简单易用的设计理念,可见一斑。下面看一个使用示例。

    package main
    
    import (
    	"fmt"
    	"github.com/groupcache/lru"
    )
    
    func main() {
    	cache := lru.New(2)
    	cache.Add("bill", 20)
    	cache.Add("dable", 19)
    	v, ok := cache.Get("bill")
    	if ok {
    		fmt.Printf("bill's age is %v
    ", v)
    	}
    	cache.Add("cat", "18")
    	
    	fmt.Printf("cache length is %d
    ", cache.Len())
    	_, ok = cache.Get("dable")
    	if !ok {
    		fmt.Printf("dable was evicted out
    ")
    	}
    }

    编译运行输出:

    bill's age is 20
    cache length is 2
    dable was evicted out

    参考文献

    [1] Github.groupcache [2] 缓存淘汰算法(LFU、LRU、ARC、FIFO、MRU)分析 [3] groupcache 源码分析(二)-- LRU

  • 相关阅读:
    android打包so文件到apk
    source build/envsetup.sh 之后
    android 应用程序 集合
    dedecms模块支持系统标签
    php中的两个DI解决方案
    yii快速入门与参考
    [ZT] 使用PHPFPM (PHP FastCGI Process Manager)来对phpcgi进程进行管理
    [转]VLD扩展使用指南
    织梦CMS安装路径问题
    php+mysql中存储过程性能简单比较
  • 原文地址:https://www.cnblogs.com/ExMan/p/12836927.html
Copyright © 2020-2023  润新知