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\n", v) } cache.Add("cat", "18") fmt.Printf("cache length is %d\n", cache.Len()) _, ok = cache.Get("dable") if !ok { fmt.Printf("dable was evicted out\n") } }

编译运行输出:

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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM