该项目github源码链接:https://github.com/muesli/cache2go
该开源项目特点是代码量少,核心代码只有三个文件,是一个用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的话,就不会被淘汰,即永久有效。
相关链接:
1)https://time-track.cn/cache2go-introduction.html