该开源项目特点是代码量少,核心代码只有三个文件,是一个用Go实现的并发安全的缓存库,适合学习读写锁、goroutine、map操作。
特性:
1)并发安全
2)可设置每条缓存的过期时间。
3)内置缓存访问次数
4)自调节的缓存过期检查
5)可设置缓存增加/删除回调函数
内容:
cacahe.go、cacheitem.go、cachetable.go、errors.go四个文件,里面包括三个结构体,以及结构体里面的属性和方法,加上对外的两个错误变量,以及两个内部使用的变量,加上一个方法。这就是这几个文件的内容。
变量:
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")
结构体:
1)CacheItem:代表一条缓存
属性
type CacheItem struct { sync.RWMutex key interface{} // 缓存项的key data interface{} // 缓存项的值 lifeSpan time.Duration // 缓存项的生命期 createdOn time.Time // 缓存项的创建时间戳 accessedOn time.Time // 缓存项上次被访问的时间戳 accessCount int64 // 缓存项被访问的次数 aboutToExpire func(key interface{}) // 缓存项被删除时的回调函数(删除之前执行)}
方法:主要包括()
1)func NewCacheItem(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem 传入值:任意类型的键名、缓存的生命周期,数据 返回值:*CacheItem 功能:创建一个新的缓存记录2)func (item *CacheItem) KeepAlive() 传入值:无 返回值:无 功能:设置上次访问时间为当前,访问次数加一3)func (item *CacheItem) LifeSpan() time.Duratio 传入值:无 返回值:缓存的生命周期4)func (item *CacheItem) AccessedOn() time.Time 传入值:无 返回值:缓存项上次被访问的时间戳 功能:获取上次访问时间戳的时间5)func (item *CacheItem) CreatedOn() time.Time 传入值:无 返回值:缓存项创建时间 功能:获取缓存项创建时间6)func (item *CacheItem) AccessCount() int64 传入值:无 返回值:缓存项访问次数 功能:获取缓存项访问次数7)func (item *CacheItem) Key() interface{} 传入值:无 返回值:缓存项的键名 功能:获取缓存项的键名8)func (item *CacheItem) Data() interface{} 传入值:无 返回值:缓存项的值 功能:获取缓存项的值9)func (item *CacheItem) SetAboutToExpireCallback(f func(interface{})) 传入值:回调函数 返回值:无 功能:设置缓存项被删除时的回调函数(删除之前执行)
2)CacheTable:代表一个缓存表,由若干条缓存表组成
属性:
type Cachetable struct { sync.RWMutex name string // 缓存表名 items map[interface{}]*CacheItem // 缓存项 cleanupTimer *time.Timer // 触发缓存清理的定时器 cleanupInterval time.Duration // 缓存清理周期 logger *log.Logger // 该缓存表的日志 // 向缓存表增加缓存项时的回调函数 loadData func(key interface{}, args ...interface{}) *CacheItem addedItem func(item *CacheItem) // 从缓存表删除一个缓存项时的回调函数}
方法:
1)func (table *CacheTable) Count() int 传入值:无 返回值:缓存表中的缓存项个数 功能:获取缓存表中的缓存项个数2)func (table *CacheTable) Foreach(trans func(key interface{}, item *CacheItem)) 传入值:要对缓存表中所有缓存键、值进行操作的函数 返回值:无 功能:遍历缓存表中的所有记录项,将遍历的内容放入到传入函数中执行3)table *CacheTable) SetDataLoader(f func(interface{}, ...interface{}) *CacheItem) 传入值:函数 返回值:无 功能:当尝试访问一个不存的key时调用该函数和0...n个附加参数被传递给回调函数4)func (table *CacheTable) SetAddedItemCallback(f func(*CacheItem)) 传入值:函数 返回值:无 功能:设置一个当向缓存表添加一个缓存项之后会调用用的函数。5)func (table *CacheTable) SetAboutToDeleteItemCallback(f func(*CacheItem)) 传入值:函数 返回值:无 功能:设置删除记录项之间会自动调用的函数,会将被删除的记录项传入6)func (table *CacheTable) SetLogger(logger *log.Logger) 传入值:日志变量 返回值:无 功能:设置日志变量7)func (table *CacheTable) Add(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem 传入值:键、过期时间、值 返回值:缓存项 功能:添加一个缓存项,并且会在添加之后自动调用之前设置的函数 如果lifeSpan为0,直接返回 如果lifeSpan比当前时间更小,则会将其键删除。8)func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) 传入值:键 返回值:被删除的缓存项 功能:删除一个缓存项,并且在删除之前会自动执行之前设置的函数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 如果记录表中不存在该键,之前又设置了访问不存在的值时的函数,则会执行那个函数,将值添加到缓存表中,如果之前没有设置那个函数,则返回nil和ErrKeyNotFound值12)func (table *CacheTable) Flush() 传入值:无 返回值:无 功能:清空缓存表,重置缓存表13)func (table *CacheTable) MostAccessed(count int64) []*CacheItem 传入值:缓存项个数 返回值:无 功能:获取缓存表中缓存项中被访问次数最多的前count 个14)func (table *CacheTable) log(v ...interface{}) 传入值:变长变量 返回值:无 功能:如果传入的为nil,则直接返回,否则打印日志
3)CacheItemPair:记录了缓存项与次数的关联
定义了type CacheItemPairList []CacheItemPair,实现了对CacheItemPair切片变量的排序,源码中主要被应用于实现MostAccessed功能。
函数:
1)Cache
func Cache(table string) *CacheTable 传入值:缓存表名 返回值:缓存表的变量 功能:如果存在这个缓存表,则返回这个缓存表,如果不存在,则新建,并返回新建号的缓存表 这个函数实现应用了单例模式
缓存表中有一个自调节的缓存过期检查实现,对其源码分析
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,即下一次启动缓存刷新的时间,从而保证可以及时的更新缓存,可以看到其实质就是自调节下一次启动缓存更新的时间。另外我们也注意到,如果lifeSpan设置为0的话,就不会被淘汰,即永久有效。
作者:laijh
链接:https://www.jianshu.com/p/a8041f009e92
共同学习,写下你的评论
评论加载中...
作者其他优质文章