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