var ( cache = make(map[string]*CacheTable) mutex sync.RWMutex )
funcCache(table string) *CacheTable { mutex.RLock() t, ok := cache[table] mutex.RUnlock()
if !ok { mutex.Lock() t, ok = cache[table] // Double check whether the table exists or not. if !ok { t = &CacheTable{ name: table, items: make(map[interface{}]*CacheItem), } cache[table] = t } mutex.Unlock() }
// The table's name. name string // 所有的 cached items 都放在items属性中 items map[interface{}]*CacheItem
// 定时器,到时见就会自动检查(遍历所有的CacheItem) cleanupTimer *time.Timer // Current timer duration. cleanupInterval time.Duration
// 日志 logger *log.Logger
// 以下是一些操作的回调函数 // Callback method triggered when trying to load a non-existing key. loadData func(key interface{}, args ...interface{}) *CacheItem // Callback method triggered when adding a new item to the cache. addedItem []func(item *CacheItem) // Callback method triggered before deleting an item from the cache. aboutToDeleteItem []func(item *CacheItem) }
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()
// 检查是否设置了添加item的回调,如果有则执行 if addedItem != nil { for _, callback := range addedItem { callback(item) } }
// If we haven't set up any expiration check timer or found a more imminent item. // 如果当前cachetable的cleanupInterval为0或者新加入的item的lifeSpan小于cleanupInterval则触发过期检查函数 if item.lifeSpan > 0 && (expDur == 0 || item.lifeSpan < expDur) { table.expirationCheck() } }
// 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 // 遍历table中的所有items,找到最先过期的那一个item for key, item := range table.items { // Cache values so we don't keep blocking the mutex. item.RLock() lifeSpan := item.lifeSpan accessedOn := item.accessedOn item.RUnlock()
// lifeSpan 永不过期 if lifeSpan == 0 { continue } if now.Sub(accessedOn) >= lifeSpan { // 已经过期删除 // Item has excessed its lifespan. table.deleteInternal(key) } else { // Find the item chronologically closest to its end-of-lifespan. // smallestDuration == 0 第一个缓存数据添加进来的时候,把第一个缓存数据的lifeSpan时间做为下次检查时间间隔 if smallestDuration == 0 || lifeSpan-now.Sub(accessedOn) < smallestDuration { smallestDuration = lifeSpan - now.Sub(accessedOn) } } }
// Setup the interval for the next cleanup run. table.cleanupInterval = smallestDuration if smallestDuration > 0 { // 启动一个goruntine定时器,在smallestDuration之后重新掉用expirationCheck方法检查 table.cleanupTimer = time.AfterFunc(smallestDuration, func() { go table.expirationCheck() }) } table.Unlock() }
if ok { // Update access counter and timestamp. r.KeepAlive() return r, nil }
// Item doesn't exist in cache. Try and fetch it with a data-loader. if loadData != nil { item := loadData(key, args...) if item != nil { table.Add(key, item.lifeSpan, item.data) return item, nil }