groupcache源碼走讀(二):實現一個可以自動過時鍵的緩存

    上文提到的緩存模型,只能在訪問鍵的時候才能判斷這個鍵是否失效,是否應該刪除。那若是一個系統須要大量使用本地緩存的話,可能會致使內存大量被無效鍵佔用,而且GC應該也不能回收這部分空間。因此有的時候就須要有一個可以自動刪除過時鍵的緩存策略。緩存

    通常地作法就是單獨啓動一個線程,用來輪訓查看當前緩存中是否有過時的鍵。筆者在此處實現的方法也差很少,經過go自己的定時器來實現。spa

//主動過時策略的cache 防止cache太大佔用內存過多
var My_Cache = &CacheSet{
        items: make(map[string]*CacheItem),
}

type CacheSet struct {
        sync.RWMutex
        items map[string]*CacheItem

        //下次緩存過時檢查的間隔時間
        expireInterval time.Duration
        //Timer responsible for triggering cleanup
        //過時清理的定時器
        expireTimer *time.Timer
}

type CacheItem struct {
        sync.RWMutex
        Key string
        Val interface{}

        //緩存鍵的存活時間和建立時間
        liveDuration time.Duration
        ctime        time.Time
}

//add new key to cache set
func (set *CacheSet) Add(key string, data interface{}, expire time.Duration) *CacheItem {
        set.Lock()

        item := &CacheItem{
                Key:          key,
                Val:          data,
                liveDuration: expire,
                ctime:        time.Now(),
        }

        set.items[key] = item
        nextExpire := set.expireInterval
        set.Unlock()

        //知足如下條件須要當即開啓緩存過時檢測
        if expire > 0 && (nextExpire == 0 || expire < nextExpire) {
                set.expireCheck()
        }

        return item
}

func (set *CacheSet) Delete(key string) bool {
        set.Lock()
        defer set.Unlock()
        if _, ok := set.items[key]; ok {
                delete(set.items, key)
        }
}

func (set *CacheSet) Get(key string) interface{} {                                                                                                                                                                                                                            
        set.RLock()
        defer set.RUnlock()
        if val, ok := set.items[key]; ok {
                return val
        }

        return nil
}

    該緩存策略會在初次插入kv或者發現當前插入的鍵的過時時間比下次過時時間要短的時候主動開啓緩存過時檢測。過時檢測代碼以下:線程

//過時檢測
func (set *CacheSet) expireCheck() {
        set.Lock()
        interval := set.expireInterval

        //當前有等待執行的定時器任務, 中止該任務並在後續啓動新任務
        if set.expireTimer == nil {
                set.expireTimer.Stop()
        }

        now := time.Now()
        nextCheckTime := 0
        for k, v := range set.items {
                // item which will never be overdue
                if v.liveDuration == 0 {
                        continue
                }

                //判斷鍵是否已通過期了,過時則刪除,未過時的話則找出下一次最近的過時時間
                if now.Sub(v.ctime) >= v.liveDuration {
                        delete(set.items, k)
                } else {
                        if nextCheckTime == 0 || nextCheckTime > (v.liveDuration-now.Sub(v.ctime)) {
                                nextCheckTime = v.liveDuration - now.Sub(v.ctime)
                        }
                }
        }

        //若是緩存中還有kv存在,設置定時器按期evict過時鍵
        if nextCheckTime > 0 {
                set.expireTimer = time.AfterFunc(nextCheckTime, func() {
                        set.expireCheck()
                })
        }
        set.Unlock()
}

    該檢測方法有兩個特性:code

  • 經過expireTimer變量來實現的, 經過使用time.AfterFunc方法,定時器會在下一次有鍵過時的時候再次啓動過時檢測方法,而且是在子協程中執行,不會阻塞當前;
  • 而且只要當前系統中仍然還有緩存,該檢測會一直持續下去。
相關文章
相關標籤/搜索