源碼:https://github.com/bluele/gcache
一、LRU
SET
GET
DELETE
Loader
二、LFU
SET
GET
DELETE
Loader
- 多種淘汰策略,LRU、LFU、simple;
- 提供loder,singleflight方式;
- 時間窗口的概念是通過整個cache表的過期時間來實現的;
- 過期刪除,訪問時才被刪除;
- 過期和淘汰策略是分開的:訪問過期會刪除,添加元素會淘汰(淘汰時不看過期時間);
- LRU/LFU更新記錄時開銷O(1),假如用堆之類相比開銷很大;
大鎖的改進:
讀寫鎖
鎖的粒度,整個map,單獨item
鎖的實現:CAS
一、LRU
LRU淘汰的思想是近期不被訪問的元素,在未來也不太會被訪問,時間的局部性。
缺點就是低頻、偶發的訪問會干擾,因為訪問一次就會被放在最熱,“容錯性”較差。
// Discards the least recently used items first.
type LRUCache struct {
baseCache
items map[interface{}]*list.Element //k-(lruItem為value的鏈表節點)
evictList *list.List. //維護順序的雙向鏈表
}
type lruItem struct {
clock Clock
key interface{}
value interface{}
expiration *time.Time
}
SET
更新位置,更新value,更新時間
檢查淘汰,新增節點,更新時間;
// set a new key-value pair
func (c *LRUCache) Set(key, value interface{}) error {
c.mu.Lock()
defer c.mu.Unlock()
_, err := c.set(key, value)
return err
}
func (c *LRUCache) set(key, value interface{}) (interface{}, error) {
var err error
// Check for existing item
var item *lruItem
if it, ok := c.items[key]; ok {
c.evictList.MoveToFront(it) //前置
item = it.Value.(*lruItem)
item.value = value
} else {
// Verify size not exceeded
if c.evictList.Len() >= c.size {
c.evict(1) //淘汰
}
item = &lruItem{
clock: c.clock,
key: key,
value: value,
}
c.items[key] = c.evictList.PushFront(item) //添加元素
}
//如果item沒有過期時間,就使用整個緩存的過期時間
if c.expiration != nil {
t := c.clock.Now().Add(*c.expiration)
item.expiration = &t
}
if c.addedFunc != nil {
c.addedFunc(key, value)
}
return item, nil
}
// Set a new key-value pair with an expiration time
func (c *LRUCache) SetWithExpire(key, value interface{}, expiration time.Duration) error {
c.mu.Lock()
defer c.mu.Unlock()
item, err := c.set(key, value)
if err != nil {
return err
}
t := c.clock.Now().Add(expiration)
item.(*lruItem).expiration = &t
return nil
}
淘汰
鏈表的實現方便清除一個/多個;
// evict removes the oldest item from the cache.
func (c *LRUCache) evict(count int) {
for i := 0; i < count; i++ {
ent := c.evictList.Back()
if ent == nil {
return
} else {
c.removeElement(ent)
}
}
}
GET
// Get a value from cache pool using key if it exists.
// If it dose not exists key and has LoaderFunc,
// generate a value using `LoaderFunc` method returns value.
func (c *LRUCache) Get(key interface{}) (interface{}, error) {
v, err := c.get(key, false)
if err == KeyNotFoundError {
return c.getWithLoader(key, true)
}
return v, err
}
func (c *LRUCache) getValue(key interface{}, onLoad bool) (interface{}, error) {
c.mu.Lock()
item, ok := c.items[key]
if ok { //命中緩存
it := item.Value.(*lruItem)
if !it.IsExpired(nil) { //沒有過期
c.evictList.MoveToFront(item) //更新順序
v := it.value
c.mu.Unlock()
if !onLoad {
c.stats.IncrHitCount()
}
return v, nil
}
//過期刪除
c.removeElement(item)
}
c.mu.Unlock()
if !onLoad {
c.stats.IncrMissCount()
}
return nil, KeyNotFoundError
}
DELETE
// Remove removes the provided key from the cache.
func (c *LRUCache) Remove(key interface{}) bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.remove(key)
}
func (c *LRUCache) remove(key interface{}) bool {
if ent, ok := c.items[key]; ok {
c.removeElement(ent)
return true
}
return false
}
func (c *LRUCache) removeElement(e *list.Element) {
c.evictList.Remove(e)
entry := e.Value.(*lruItem)
delete(c.items, entry.key)
if c.evictedFunc != nil {
entry := e.Value.(*lruItem)
c.evictedFunc(entry.key, entry.value)
}
}
Loader
- 用戶提供loader方式,loaderExpireFunc可以基于key獲取到對應的value;
// Set a loader function.
// loaderFunc: create a new value with this function if cached value is expired.
func (cb *CacheBuilder) LoaderFunc(loaderFunc LoaderFunc) *CacheBuilder {
cb.loaderExpireFunc = func(k interface{}) (interface{}, *time.Duration, error) {
v, err := loaderFunc(k)
return v, nil, err
}
return cb
}
- 提供統一的加載方法,使用singleflight,執行c.loaderExpireFunc(key)。
此處提供了cb的封裝函數,可以針對獲取的value做不同的處理;比如直接返回用戶,或者加入緩存。
// load a new value using by specified key.
func (c *baseCache) load(key interface{}, cb func(interface{}, *time.Duration, error) (interface{}, error), isWait bool) (interface{}, bool, error) {
v, called, err := c.loadGroup.Do(key, func() (v interface{}, e error) {
defer func() {
if r := recover(); r != nil {
e = fmt.Errorf("Loader panics: %v", r)
}
}()
return cb(c.loaderExpireFunc(key))
}, isWait)
if err != nil {
return nil, called, err
}
return v, called, nil
}
- LRU的loder
func (c *LRUCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) {
if c.loaderExpireFunc == nil {
return nil, KeyNotFoundError
}
value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) {
if e != nil {
return nil, e
}
c.mu.Lock()
defer c.mu.Unlock()
item, err := c.set(key, v)
if err != nil {
return nil, err
}
if expiration != nil {
t := c.clock.Now().Add(*expiration)
item.(*lruItem).expiration = &t
}
return v, nil
}, isWait)
if err != nil {
return nil, err
}
return value, nil
}
二、LFU
LFU是基于訪問次數來淘汰元素,所以需要額外維護訪問次數的順序。
-
可以基于map+堆,但是堆的操作開銷是o(logn)的,開銷過大;
- 需要一種O(1)時間的LFU算法,論文:http://dhruvbird.com/lfu.pdf;
掛在次數為1的元素可以用鏈表實現,gcache使用的map簡單;
用鏈表可以基于過期時間再排序,就是需要額外的開銷了。
// Discards the least frequently used items first.
type LFUCache struct {
baseCache
items map[interface{}]*lfuItem
freqList *list.List // list for freqEntry
}
type lfuItem struct {
clock Clock
key interface{}
value interface{}
freqElement *list.Element
expiration *time.Time
}
//每個訪問次數單元,維護多個次數相同的lfuItem
type freqEntry struct {
freq uint
items map[*lfuItem]struct{}
}
SET
// Set a new key-value pair with an expiration time
func (c *LFUCache) SetWithExpire(key, value interface{}, expiration time.Duration) error {
c.mu.Lock()
defer c.mu.Unlock()
item, err := c.set(key, value)
if err != nil {
return err
}
t := c.clock.Now().Add(expiration)
item.(*lfuItem).expiration = &t
return nil
}
func (c *LFUCache) set(key, value interface{}) (interface{}, error) {
var err error
// Check for existing item
item, ok := c.items[key]
if ok {
item.value = value
} else {
// Verify size not exceeded
if len(c.items) >= c.size {
c.evict(1)
}
item = &lfuItem{
clock: c.clock,
key: key,
value: value,
freqElement: nil,
}
el := c.freqList.Front()
fe := el.Value.(*freqEntry)
fe.items[item] = struct{}{}
item.freqElement = el
c.items[key] = item
}
if c.expiration != nil {
t := c.clock.Now().Add(*c.expiration)
item.expiration = &t
}
if c.addedFunc != nil {
c.addedFunc(key, value)
}
return item, nil
}
淘汰
淘汰的過程中,只是遍歷隨機淘汰,并不是按照過期時間;
// evict removes the least frequence item from the cache.
func (c *LFUCache) evict(count int) {
entry := c.freqList.Front()
for i := 0; i < count; {
if entry == nil {
return
} else {
for item, _ := range entry.Value.(*freqEntry).items {
if i >= count {
return
}
c.removeItem(item)
i++
}
entry = entry.Next()
}
}
}
GET
過期刪除;
命中緩存,更新頻次;
// Get a value from cache pool using key if it exists.
// If it dose not exists key and has LoaderFunc,
// generate a value using `LoaderFunc` method returns value.
func (c *LFUCache) Get(key interface{}) (interface{}, error) {
v, err := c.get(key, false)
if err == KeyNotFoundError {
return c.getWithLoader(key, true)
}
return v, err
}
func (c *LFUCache) getValue(key interface{}, onLoad bool) (interface{}, error) {
c.mu.Lock()
item, ok := c.items[key]
if ok {
if !item.IsExpired(nil) {
c.increment(item)
v := item.value
c.mu.Unlock()
if !onLoad {
c.stats.IncrHitCount()
}
return v, nil
}
c.removeItem(item)
}
c.mu.Unlock()
if !onLoad {
c.stats.IncrMissCount()
}
return nil, KeyNotFoundError
}
頻次更新
func (c *LFUCache) increment(item *lfuItem) {
currentFreqElement := item.freqElement //當前lfuItem對應的鏈表節點
currentFreqEntry := currentFreqElement.Value.(*freqEntry)//當前鏈表節點的freqEntry
nextFreq := currentFreqEntry.freq + 1 //計算頻次
delete(currentFreqEntry.items, item) //當前freqEntry刪除這個lfuItem
nextFreqElement := currentFreqElement.Next()//獲取下一個鏈表節點
if nextFreqElement == nil {//創建新的節點,按頻次從小到大
nextFreqElement = c.freqList.InsertAfter(&freqEntry{
freq: nextFreq,
items: make(map[*lfuItem]struct{}),
}, currentFreqElement)
}
nextFreqElement.Value.(*freqEntry).items[item] = struct{}{}//下一個freqEntry記錄lfuItem
item.freqElement = nextFreqElement //lfuItem綁定到新的鏈表節點
}
DELETE
// Remove removes the provided key from the cache.
func (c *LFUCache) Remove(key interface{}) bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.remove(key)
}
func (c *LFUCache) remove(key interface{}) bool {
if item, ok := c.items[key]; ok {
c.removeItem(item)
return true
}
return false
}
// removeElement is used to remove a given list element from the cache
func (c *LFUCache) removeItem(item *lfuItem) {
delete(c.items, item.key) //刪除map的關系
delete(item.freqElement.Value.(*freqEntry).items, item) //刪除freq的關系
if c.evictedFunc != nil {
c.evictedFunc(item.key, item.value)
}
}
Loader
LFU的loader跟LRU相同,加載數據,存入緩存。
func (c *LFUCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) {
if c.loaderExpireFunc == nil {
return nil, KeyNotFoundError
}
value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) {
if e != nil {
return nil, e
}
c.mu.Lock()
defer c.mu.Unlock()
item, err := c.set(key, v)
if err != nil {
return nil, err
}
if expiration != nil {
t := c.clock.Now().Add(*expiration)
item.(*lfuItem).expiration = &t
}
return v, nil
}, isWait)
if err != nil {
return nil, err
}
return value, nil
}