go cache2go分析

該項目github源碼鏈接:https://github.com/muesli/cache2go

該開源項目特點是代碼量少疲吸,核心代碼只有三個文件鲸鹦,是一個用Go實現(xiàn)的并發(fā)安全的緩存庫斤富,適合學(xué)習(xí)讀寫鎖葵诈、goroutine塑悼、map操作劲适。

特性:
1)并發(fā)安全
2)可設(shè)置每條緩存的過期時間。
3)內(nèi)置緩存訪問次數(shù)
4)自調(diào)節(jié)的緩存過期檢查
5)可設(shè)置緩存增加/刪除回調(diào)函數(shù)

內(nèi)容:
cacahe.go厢蒜、cacheitem.go霞势、cachetable.go烹植、errors.go四個文件,里面包括三個結(jié)構(gòu)體愕贡,以及結(jié)構(gòu)體里面的屬性和方法草雕,加上對外的兩個錯誤變量,以及兩個內(nèi)部使用的變量固以,加上一個方法墩虹。這就是這幾個文件的內(nèi)容。

變量:
1)ErrKeyNotFound

// 鍵沒有在緩存表中找到
ErrKeyNotFound = errors.New("Key not found in cache")
// 

2)ErrKeyNotFoundOrLoadable

// 鍵沒有被找到和也不能被加載進緩存中
ErrKeyNotFoundOrLoadable = errors.New("Key not found and could not be loaded into cache")

結(jié)構(gòu)體:
1)CacheItem:代表一條緩存
屬性

type CacheItem struct {
    sync.RWMutex
    
    key interface{}         // 緩存項的key
    data interface{}        // 緩存項的值
    lifeSpan time.Duration  // 緩存項的生命期
    
    createdOn time.Time     // 緩存項的創(chuàng)建時間戳
    accessedOn time.Time    // 緩存項上次被訪問的時間戳
    accessCount int64       // 緩存項被訪問的次數(shù)
    
    aboutToExpire func(key interface{}) // 緩存項被刪除時的回調(diào)函數(shù)(刪除之前執(zhí)行)
}

方法:主要包括()

1)func NewCacheItem(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem
傳入值:任意類型的鍵名憨琳、緩存的生命周期诫钓,數(shù)據(jù)
返回值:*CacheItem
功能:創(chuàng)建一個新的緩存記錄
2)func (item *CacheItem) KeepAlive()
傳入值:無
返回值:無
功能:設(shè)置上次訪問時間為當(dāng)前,訪問次數(shù)加一
3)func (item *CacheItem) LifeSpan() time.Duratio
傳入值:無
返回值:緩存的生命周期
4)func (item *CacheItem) AccessedOn() time.Time
傳入值:無
返回值:緩存項上次被訪問的時間戳
功能:獲取上次訪問時間戳的時間
5)func (item *CacheItem) CreatedOn() time.Time
傳入值:無
返回值:緩存項創(chuàng)建時間
功能:獲取緩存項創(chuàng)建時間
6)func (item *CacheItem) AccessCount() int64
傳入值:無
返回值:緩存項訪問次數(shù)
功能:獲取緩存項訪問次數(shù)
7)func (item *CacheItem) Key() interface{}
傳入值:無
返回值:緩存項的鍵名
功能:獲取緩存項的鍵名
8)func (item *CacheItem) Data() interface{} 
傳入值:無
返回值:緩存項的值
功能:獲取緩存項的值
9)func (item *CacheItem) SetAboutToExpireCallback(f func(interface{}))
傳入值:回調(diào)函數(shù)
返回值:無
功能:設(shè)置緩存項被刪除時的回調(diào)函數(shù)(刪除之前執(zhí)行)

2)CacheTable:代表一個緩存表篙螟,由若干條緩存表組成
屬性:

type Cachetable struct {
    sync.RWMutex
    
    name string                         // 緩存表名
    items map[interface{}]*CacheItem    // 緩存項
    
    cleanupTimer *time.Timer            // 觸發(fā)緩存清理的定時器
    cleanupInterval time.Duration       // 緩存清理周期
    
    logger *log.Logger                  // 該緩存表的日志
    // 向緩存表增加緩存項時的回調(diào)函數(shù)
    loadData func(key interface{}, args ...interface{}) *CacheItem
    addedItem func(item *CacheItem)     // 從緩存表刪除一個緩存項時的回調(diào)函數(shù)
}

方法:

1)func (table *CacheTable) Count() int 
傳入值:無
返回值:緩存表中的緩存項個數(shù)
功能:獲取緩存表中的緩存項個數(shù)
2)func (table *CacheTable) Foreach(trans func(key interface{}, item *CacheItem))
傳入值:要對緩存表中所有緩存鍵尖坤、值進行操作的函數(shù)
返回值:無
功能:遍歷緩存表中的所有記錄項,將遍歷的內(nèi)容放入到傳入函數(shù)中執(zhí)行
3)table *CacheTable) SetDataLoader(f func(interface{}, ...interface{}) *CacheItem)
傳入值:函數(shù)
返回值:無
功能:當(dāng)嘗試訪問一個不存的key時調(diào)用該函數(shù)和0...n個附加參數(shù)被傳遞給回調(diào)函數(shù)
4)func (table *CacheTable) SetAddedItemCallback(f func(*CacheItem))
傳入值:函數(shù)
返回值:無
功能:設(shè)置一個當(dāng)向緩存表添加一個緩存項之后會調(diào)用用的函數(shù)闲擦。
5)func (table *CacheTable) SetAboutToDeleteItemCallback(f func(*CacheItem)) 
傳入值:函數(shù)
返回值:無
功能:設(shè)置刪除記錄項之間會自動調(diào)用的函數(shù),會將被刪除的記錄項傳入
6)func (table *CacheTable) SetLogger(logger *log.Logger)
傳入值:日志變量
返回值:無
功能:設(shè)置日志變量
7)func (table *CacheTable) Add(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem
傳入值:鍵场梆、過期時間墅冷、值
返回值:緩存項
功能:添加一個緩存項,并且會在添加之后自動調(diào)用之前設(shè)置的函數(shù)
如果lifeSpan為0或油,直接返回
如果lifeSpan比當(dāng)前時間更小寞忿,則會將其鍵刪除。

8)func (table *CacheTable) Delete(key interface{}) (*CacheItem, error)
傳入值:鍵
返回值:被刪除的緩存項
功能:刪除一個緩存項顶岸,并且在刪除之前會自動執(zhí)行之前設(shè)置的函數(shù)
9)func (table *CacheTable) Exists(key interface{}) bool
傳入值:鍵
返回值:bool
功能:判斷緩存項是否存在
10)func (table *CacheTable) NotFoundAdd(key interface{}, lifeSpan time.Duration, data interface{}) bool
傳入值:
返回值:
功能:如果緩存表中不存在該緩存項腔彰,則添加到緩存表中并返回true,如果存在緩存項辖佣,則不做操作霹抛,返回false。
11)func (table *CacheTable) Value(key interface{}, args ...interface{}) (*CacheItem, error)
傳入值:鍵值卷谈、以及若干個其他值
返回值:緩存項指針和遇到的錯誤信息
功能:
         如果記錄表中存在該鍵杯拐,則返回緩存項及nil
         如果記錄表中不存在該鍵,之前又設(shè)置了訪問不存在的值時的函數(shù)世蔗,則會執(zhí)行那個函數(shù)端逼,將值添加到緩存表中,如果之前沒有設(shè)置那個函數(shù)污淋,則返回nil和ErrKeyNotFound值
12)func (table *CacheTable) Flush()
傳入值:無
返回值:無
功能:清空緩存表顶滩,重置緩存表
13)func (table *CacheTable) MostAccessed(count int64) []*CacheItem
傳入值:緩存項個數(shù)
返回值:無
功能:獲取緩存表中緩存項中被訪問次數(shù)最多的前count 個
14)func (table *CacheTable) log(v ...interface{})
傳入值:變長變量
返回值:無
功能:如果傳入的為nil,則直接返回寸爆,否則打印日志

3)CacheItemPair:記錄了緩存項與次數(shù)的關(guān)聯(lián)
定義了type CacheItemPairList []CacheItemPair礁鲁,實現(xiàn)了對CacheItemPair切片變量的排序盐欺,源碼中主要被應(yīng)用于實現(xiàn)MostAccessed功能。
函數(shù):
1)Cache

func Cache(table string) *CacheTable
傳入值:緩存表名
返回值:緩存表的變量
功能:如果存在這個緩存表救氯,則返回這個緩存表找田,如果不存在,則新建着憨,并返回新建號的緩存表

這個函數(shù)實現(xiàn)應(yīng)用了單例模式

緩存表中有一個自調(diào)節(jié)的緩存過期檢查實現(xiàn)墩衙,對其源碼分析

func (table *CacheTable) expirationCheck() {
    table.Lock()
    if table.cleanupTimer != nil {
        table.cleanupTimer.Stop()
    }
    if table.cleanupInterval > 0 {
        table.log("Expiration check triggered after", table.cleanupInterval, "for table", table.name)
    } else {
        table.log("Expiration check installed for table", table.name)
    }

    // Cache value so we don't keep blocking the mutex.
    items := table.items
    table.Unlock()

    // To be more accurate with timers, we would need to update 'now' on every
    // loop iteration. Not sure it's really efficient though.
    now := time.Now()
    smallestDuration := 0 * time.Second
    for key, item := range items {
        // Cache values so we don't keep blocking the mutex.
        item.RLock()
        lifeSpan := item.lifeSpan
        accessedOn := item.accessedOn
        item.RUnlock()

        if lifeSpan == 0 {
            continue
        }
        if now.Sub(accessedOn) >= lifeSpan {
            // Item has excessed its lifespan.
            table.Delete(key)
        } else {
            // Find the item chronologically closest to its end-of-lifespan.
            if smallestDuration == 0 || lifeSpan-now.Sub(accessedOn) < smallestDuration {
                smallestDuration = lifeSpan - now.Sub(accessedOn)
            }
        }
    }

    // Setup the interval for the next cleanup run.
    table.Lock()
    table.cleanupInterval = smallestDuration
    if smallestDuration > 0 {
        table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
            go table.expirationCheck()
        })
    }
    table.Unlock()
}
func (table *CacheTable) addInternal(item *CacheItem) {
    // Careful: do not run this method unless the table-mutex is locked!
    // It will unlock it for the caller before running the callbacks and checks
    table.log("Adding item with key", item.key, "and lifespan of", item.lifeSpan, "to table", table.name)
    table.items[item.key] = item

    // Cache values so we don't keep blocking the mutex.
    expDur := table.cleanupInterval
    addedItem := table.addedItem
    table.Unlock()

    // Trigger callback after adding an item to cache.
    if addedItem != nil {
        addedItem(item)
    }

    // If we haven't set up any expiration check timer or found a more imminent item.
    if item.lifeSpan > 0 && (expDur == 0 || item.lifeSpan < expDur) {
        table.expirationCheck()
    }
}

func (table *CacheTable) Add(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {
    item := NewCacheItem(key, lifeSpan, data)

    // Add item to cache.
    table.Lock()
    table.addInternal(item)

    return item
}

入口:緩存表的Add方法->addInternal->expirationCheck
代碼中會去遍歷所有緩存項,找到最快要被淘汰掉的緩存項的的時間作為cleanupInterval甲抖,即下一次啟動緩存刷新的時間漆改,從而保證可以及時的更新緩存,可以看到其實質(zhì)就是自調(diào)節(jié)下一次啟動緩存更新的時間准谚。另外我們也注意到挫剑,如果lifeSpan設(shè)置為0的話,就不會被淘汰柱衔,即永久有效樊破。
相關(guān)鏈接:
1)https://time-track.cn/cache2go-introduction.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市唆铐,隨后出現(xiàn)的幾起案子哲戚,更是在濱河造成了極大的恐慌,老刑警劉巖艾岂,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件顺少,死亡現(xiàn)場離奇詭異,居然都是意外死亡王浴,警方通過查閱死者的電腦和手機脆炎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氓辣,“玉大人秒裕,你說我怎么就攤上這事〕ィ” “怎么了簇爆?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長爽撒。 經(jīng)常有香客問我入蛆,道長,這世上最難降的妖魔是什么硕勿? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任哨毁,我火速辦了婚禮,結(jié)果婚禮上源武,老公的妹妹穿的比我還像新娘扼褪。我一直安慰自己想幻,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布话浇。 她就那樣靜靜地躺著脏毯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪幔崖。 梳的紋絲不亂的頭發(fā)上食店,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音赏寇,去河邊找鬼吉嫩。 笑死,一個胖子當(dāng)著我的面吹牛嗅定,可吹牛的內(nèi)容都是我干的自娩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼渠退,長吁一口氣:“原來是場噩夢啊……” “哼忙迁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起碎乃,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤动漾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后荠锭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡晨川,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年证九,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片共虑。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡愧怜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妈拌,到底是詐尸還是另有隱情拥坛,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布尘分,位于F島的核電站猜惋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏培愁。R本人自食惡果不足惜著摔,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望定续。 院中可真熱鬧谍咆,春花似錦禾锤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至供嚎,卻和暖如春黄娘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背查坪。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工寸宏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人偿曙。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓氮凝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親望忆。 傳聞我的和親對象是個殘疾皇子罩阵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348