該項目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