typeEvictCallbackfunc(keyinterface{},valueinterface{})//用於在 LRU 在 Evict entry 時call back 給使用者知道
// 實作了none thread safhe 的固定大小 LRU cahe
typeLRUstruct{sizeint//cache size
evictList*list.List//lru list
itemsmap[interface{}]*list.Element//紀錄 lru key 對應到的 value
onEvictEvictCallback//當發生LRU Evict entry 時call back 給使用者知道
}// LRU cache 所記錄的物件型態
typeentrystruct{keyinterface{}valueinterface{}}
可能有人會問說為什麼會有一個 map 在 LRU 的資料結構中呢?
原因是從 map 裡面找資料的速度是 O(1) 的,我們可以從 map 中判斷 key 是否存在,如果存在的話只要更新 map 中 key 所對應的 value ,並且將本來就存在 Doubly linked list 的 value 移動到 header 。
簡單看完資料結構,我們可以看看要怎麼把這個物件叫起來,裡面有沒有偷偷藏什麼不為人知的初始化呢?
New Function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// NewLRU constructs an LRU of the given size
funcNewLRU(sizeint,onEvictEvictCallback)(*LRU,error){//由於是固定 size 的 cahce 所以 size 不能小於0
ifsize<=0{returnnil,errors.New("Must provide a positive size")}c:=&LRU{size:size,evictList:list.New(),//使用 go 內建的 list 資料結構
items:make(map[interface{}]*list.Element),onEvict:onEvict,//傳入EvictCallback 當發生 Evict 的時候可以通知使用者處理
}returnc,nil}
// Add adds a value to the cache. Returns true if an eviction occurred.
func(c*LRU)Add(key,valueinterface{})(evictedbool){// Check for existing item
//檢查物件是否存在於 map 中,若是存在的話我們需要把 LRU 中 物件所在的位置移動到 list 的 header
//並且更新 物件 的值
ifent,ok:=c.items[key];ok{c.evictList.MoveToFront(ent)ent.Value.(*entry).value=valuereturnfalse}//若是物件不存在於 map 當中,我們需要先建立物件在存放在 LRU 的 entry
//並請把 entry 推送到 LRU list 的 header
//最後更新 map 下次物件進來的時候可以直接從 map 判斷物件是否存在過,若是存在就直接更新 LRU list
ent:=&entry{key,value}entry:=c.evictList.PushFront(ent)c.items[key]=entry//更新完 LRU 後需要判斷 LRU 的長度是否超過預設值,超過的話就需要刪除 LRU
// list 最後一個 entry 並且更新對應的 map (過程都在c.removeOldest(),晚點會看到)
evict:=c.evictList.Len()>c.sizeifevict{c.removeOldest()}returnevict}
Get
1
2
3
4
5
6
7
8
9
10
11
// Get looks up a key's value from the cache.
func(c*LRU)Get(keyinterface{})(valueinterface{},okbool){// 會先從 map 搜尋物件是否存在,不存在就不用拿拉xD
ifent,ok:=c.items[key];ok{// 根據 LRU 演算法最近使用過的物件要推到 list 的 header
c.evictList.MoveToFront(ent)returnent.Value.(*entry).value,true}return}
Contains
1
2
3
4
5
6
7
// Contains checks if a key is in the cache, without updating the recent-ness
// or deleting it for being stale.
func(c*LRU)Contains(keyinterface{})(okbool){_,ok=c.items[key]//從 lru map 中查看有沒有我們指定的 key
returnok}
Peek
1
2
3
4
5
6
7
8
9
// Peek returns the key value (or undefined if not found) without updating
// the "recently used"-ness of the key.
func(c*LRU)Peek(keyinterface{})(valueinterface{},okbool){varent*list.Element//先建立一個 element 等著承裝 LRU 的資料
ifent,ok=c.items[key];ok{//從 LRU map 中找到對應的資料後回傳
returnent.Value.(*entry).value,true}returnnil,ok}
Remove
1
2
3
4
5
6
7
8
9
10
11
// Remove removes the provided key from the cache, returning if the
// key was contained.
func(c*LRU)Remove(keyinterface{})(presentbool){// 會先從 map 搜尋物件是否存在,不存在就不用拿拉xD
ifent,ok:=c.items[key];ok{// 直接從 LRU list 與 map 移除物件(過程都在c.removeElement(),晚點會看到)
c.removeElement(ent)returntrue}returnfalse}
RemoveOldest
1
2
3
4
5
6
7
8
9
10
11
// RemoveOldest removes the oldest item from the cache.
func(c*LRU)RemoveOldest()(keyinterface{},valueinterface{},okbool){ent:=c.evictList.Back()//取得LRU list 最最舊的資料
//如果有找到資料就刪除最舊的資料
ifent!=nil{c.removeElement(ent)kv:=ent.Value.(*entry)returnkv.key,kv.value,true}returnnil,nil,false}
GetOldest
1
2
3
4
5
6
7
8
9
10
// GetOldest returns the oldest entry
func(c*LRU)GetOldest()(keyinterface{},valueinterface{},okbool){ent:=c.evictList.Back()//取得LRU list 最最舊的資料
//如果有找到資料就回傳
ifent!=nil{kv:=ent.Value.(*entry)returnkv.key,kv.value,true}returnnil,nil,false}
Keys
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Keys returns a slice of the keys in the cache, from oldest to newest.
func(c*LRU)Keys()[]interface{}{//先建立一個 slice 等等用來 copy LRU 資料用
keys:=make([]interface{},len(c.items))i:=0// 會從 LRU list 最後一個 entry 開始往前找,直到往前找到nil為止
// 過程中會把 entry 的 key 列出來放置到 keys 的 slice 中
// 但我有個疑問,不是有 map 嗎?直接拿 map 應該會比 LRU list 一個一格找要快吧?
forent:=c.evictList.Back();ent!=nil;ent=ent.Prev(){keys[i]=ent.Value.(*entry).keyi++}returnkeys}
Len
1
2
3
4
// Len returns the number of items in the cache.
func(c*LRU)Len()int{returnc.evictList.Len()//回傳 LRU list 長度
}
removeOldest/removeElement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// removeOldest removes the oldest item from the cache.
func(c*LRU)removeOldest(){ent:=c.evictList.Back()//從 LRU list 中取得最舊的資料
//如果剛剛有找到存在的資料話就刪除物件
ifent!=nil{c.removeElement(ent)}}// removeElement is used to remove a given list element from the cache
func(c*LRU)removeElement(e*list.Element){c.evictList.Remove(e)//刪除 LRU list 的資料
kv:=e.Value.(*entry)//把list.Element轉成entry
delete(c.items,kv.key)//透過 entry key 刪除 LRU map 對應的 物件
ifc.onEvict!=nil{c.onEvict(kv.key,kv.value)//因為有物件被刪除了需要通知使用者
}}
// Cache is a thread-safe fixed size LRU cache.
typeCachestruct{lrusimplelru.LRUCache//組合了 simple lru 的功能
locksync.RWMutex//多了讀寫鎖
}
其實很簡單 xDD 單純多了讀寫鎖而已,接著來看怎麼建構起這個物件的。
New function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// New creates an LRU of the given size.
funcNew(sizeint)(*Cache,error){returnNewWithEvict(size,nil)//最簡單的方法就是直接就入 cahce size ,不指定 Evict call back function
}// NewWithEvict constructs a fixed size cache with the given eviction
// callback.
funcNewWithEvict(sizeint,onEvictedfunc(keyinterface{},valueinterface{}))(*Cache,error){lru,err:=simplelru.NewLRU(size,simplelru.EvictCallback(onEvicted))//這邊就是有指定 Evict call back function
//當發生 Evict 事件時會透過 call back function 通知調用者。
iferr!=nil{returnnil,err}c:=&Cache{lru:lru,}returnc,nil}
// Purge is used to completely clear the cache.
func(c*Cache)Purge(){c.lock.Lock()//全部移除前上鎖
c.lru.Purge()//lru 移除
c.lock.Unlock()//移除完解鎖
}// Add adds a value to the cache. Returns true if an eviction occurred.
func(c*Cache)Add(key,valueinterface{})(evictedbool){c.lock.Lock()//加入資料前上鎖
evicted=c.lru.Add(key,value)//呼叫 simple lru add function
c.lock.Unlock()//加完資料解鎖
returnevicted}// Get looks up a key's value from the cache.
func(c*Cache)Get(keyinterface{})(valueinterface{},okbool){c.lock.Lock()//拿資料前上鎖
value,ok=c.lru.Get(key)//呼叫 simple lru get function
c.lock.Unlock()//拿完資料解鎖
returnvalue,ok}//這裡可能有人會問 Get 這個 function 不是拿資料嗎?為什麼用 lock 而不是 Rlock????
//別忘了 LRU 在拿資料的時候會更新資料的熱度,讓最近取得過的資料移到 LRU 的第一位!
// Contains checks if a key is in the cache, without updating the
// recent-ness or deleting it for being stale.
func(c*Cache)Contains(keyinterface{})bool{c.lock.RLock()//單純拿資料,不會動到其他資料用 rlock
containKey:=c.lru.Contains(key)//呼叫 simple lru contain function
c.lock.RUnlock()//拿完資料解鎖
returncontainKey}
幾本上就做了簡單的封裝也沒有做什麼特別的處理,其他的 function 可以到 hashicorp/golang-lru 的 github 去查看看。
// LRUExpireCache is a cache that ensures the mostly recently accessed keys are returned with
// a ttl beyond which keys are forcibly expired.
typeLRUExpireCachestruct{// clock is used to obtain the current time
clockClock//用來定時驅除 LRU 內的物件
cache*lru.Cache//用來定時驅除 LRU 內的物件
locksync.Mutex//多了鎖
}
放入的物件除了 key 所代表的 value 之外還多加了 expireTime 來看, cache 是否過期。
// NewLRUExpireCache creates an expiring cache with the given size
funcNewLRUExpireCache(maxSizeint)*LRUExpireCache{//套入 kubernetes 的 real clock ,有機會再來看 clock ,這裡就當作他是 times就好了
returnNewLRUExpireCacheWithClock(maxSize,realClock{})}// NewLRUExpireCacheWithClock creates an expiring cache with the given size, using the specified clock to obtain the current time.
funcNewLRUExpireCacheWithClock(maxSizeint,clockClock)*LRUExpireCache{cache,err:=lru.New(maxSize)//這裡直接重用了 hashicorp/golang-lru ,建立一個 cache
iferr!=nil{// if called with an invalid size
panic(err)}return&LRUExpireCache{clock:clock,cache:cache}}
func(c*LRUExpireCache)Get(keyinterface{})(interface{},bool){c.lock.Lock()deferc.lock.Unlock()e,ok:=c.cache.Get(key)//重用hashicorp/golang-lru 的 get 取得物件
//沒東西的話直接回傳就好
if!ok{returnnil,false}//需要額外判斷物件 ttl 是否過期,若是過期需要移出 LRU 並回傳使用者 找不到
ifc.clock.Now().After(e.(*cacheEntry).expireTime){c.cache.Remove(key)returnnil,false}//沒過期直接回傳
returne.(*cacheEntry).value,true}// Get looks up a key's value from the cache.
func(c*Cache)Get(keyinterface{})(valueinterface{},okbool){c.lock.Lock()value,ok=c.lru.Get(key)//重用hashicorp/golang-lru 的 get 取得物件
c.lock.Unlock()returnvalue,ok}
Remove
1
2
3
4
5
6
// Remove removes the specified key from the cache if it exists
func(c*LRUExpireCache)Remove(keyinterface{}){c.lock.Lock()deferc.lock.Unlock()c.cache.Remove(key)//重用hashicorp/golang-lru 的 remove 移除物件
}
Keys
1
2
3
4
5
6
7
8
// Keys returns all the keys in the cache, even if they are expired. Subsequent calls to
// get may return not found. It returns all keys from oldest to newest.
func(c*LRUExpireCache)Keys()[]interface{}{c.lock.Lock()deferc.lock.Unlock()returnc.cache.Keys()//重用hashicorp/golang-lru 的 keys 取得所有 keys
}