在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
1. Cache2go 是什么
2. 项目结构
3. 关键数据结构
3.1 CacheItem
// CacheItem 是一个单独的缓存条目 // 参数 data 包含用户设置在缓存里的值. type CacheItem struct { sync.RWMutex // 该项的键 key interface{} // 该项的值 data interface{} // 不能被访问后的存活时间 lifeSpan time.Duration // 被创建的时间戳 createdOn time.Time // 上一次被访问的时间戳 accessedOn time.Time // 多久被访问一次 accessCount int64 // 被删除时触发的回调方法 aboutToExpire []func(key interface{}) } 3.2 CacheTable
// CacheTable 是缓存中的一个表 type CacheTable struct { sync.RWMutex // 表的名称 name string // 所有的缓存项 items map[interface{}]*CacheItem // 负责触发清除操作的计时器 cleanupTimer *time.Timer // 当前清除操作触发的时间间隔 cleanupInterval time.Duration // The logger used for this table. logger *log.Logger // 需要提取一个不存在的key时触发的回调函数 loadData func(key interface{}, args ...interface{}) *CacheItem // 增加一个缓存条目时触发的回调函数 addedItem []func(item *CacheItem) // 从缓存中删除条目前触发的回调函数 aboutToDeleteItem []func(item *CacheItem) } 4. 代码逻辑4.1 CacheItem
4.2 CacheTable
// Expiration check loop, triggered by a self-adjusting timer. 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) } // 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 table.items { // Cache values so we don't keep blocking the mutex. item.RLock() lifeSpan := item.lifeSpan accessedOn := item.accessedOn item.RUnlock() // 存活时间为 0 表示一直存活 if lifeSpan == 0 { continue } // 计算当前时间和上一次访问的时间差是否超过存活时间 if now.Sub(accessedOn) >= lifeSpan { // 过期即可删除 table.deleteInternal(key) } else { // 未超期则计算最接近过期时间的条目还差多久过期 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 { // 如果均不需要过期则无需执行后续检查,否则在最小时间间隔后异步再次执行检查 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 { for _, callback := range addedItem { callback(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() } }
4.3 cache.go// 表存在则返回表,否则则创建一个表再返回 func Cache(table string) *CacheTable { mutex.RLock() t, ok := cache[table] mutex.RUnlock() if !ok { mutex.Lock() t, ok = cache[table] // 表不存在时需要创建一个空表,二次加锁检查为了并发安全 if !ok { t = &CacheTable{ name: table, items: make(map[interface{}]*CacheItem), } cache[table] = t } mutex.Unlock() } return t } 5. 参考原文 |
请发表评论