欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > cache2go源码分析

cache2go源码分析

2024/10/23 23:32:52 来源:https://blog.csdn.net/qq_43163694/article/details/139907720  浏览:    关键词:cache2go源码分析

前言

cache2go是一个优秀的开源项目,具有过期功能的并发安全的golang缓存库。

源码分析

定义结构

定义一个缓存表

type CacheTable struct {sync.RWMutex// 一个表名name string// 全部缓存项items map[interface{}]*CacheItem// 清理时间触发cleanupTimer *time.Timer// 清理时间间隔cleanupInterval time.Durationlogger *log.Logger// 尝试不存在key的时候回调的方法loadData func(key interface{}, args ...interface{}) *CacheItem// 加入缓存时回调方法addedItem []func(item *CacheItem)// 删除缓存时回调方aboutToDeleteItem []func(item *CacheItem)
}

定义一个缓存数据结构

type CacheItem struct {sync.RWMutex// 一个缓存的keykey interface{}// 一个缓存的数据data interface{}// 存在多久没有被访问lifeSpan time.Duration// 创建时间createdOn time.Time// 最后被访问的时间accessedOn time.Time// 访问次数accessCount int64// 在从缓存中删除项目之前触发的回调方法。aboutToExpire []func(key interface{})
}

创建缓存表

var (cache = make(map[string]*CacheTable)mutex sync.RWMutex
)func Cache(table string) *CacheTable {mutex.RLock()t, ok := cache[table]mutex.RUnlock()// 如果map的key不存在,就要创建if !ok {mutex.Lock()t, ok = cache[table]// 双重检查表存不存在if !ok {t = &CacheTable{name:  table,items: make(map[interface{}]*CacheItem),}}}return t
}

分析cacheitem.go代码

// 获取新缓存方法,返回缓存指针
func NewCacheItem(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {// 获取当前时间t := time.Now()return &CacheItem{key:           key,lifeSpan:      lifeSpan,createdOn:     t,accessedOn:    t,accessCount:   0,aboutToExpire: nil,data:          data,}
}// 继续存活
func (item *CacheItem) KeepAlive() {// 锁着当前缓存item.Lock()// 执行完此方法后解锁defer item.Unlock()// 更新最新时间item.accessedOn = time.Now()// 被访问一次item.accessCount++
}// 返回这缓存的有效时间
func (item *CacheItem) LifeSpan() time.Duration {// 不可变return item.lifeSpan
}// 并发安全获取缓存已存活时间
func (item *CacheItem) AccessedOn() time.Time {item.Lock()defer item.Unlock()return item.accessedOn
}// 并发安全获取缓存已访问次数
func (item *CacheItem) AccessedCount() int64 {item.Lock()defer item.Unlock()return item.AccessedCount()
}// 返回缓存key
func (item *CacheItem) Key() interface{} {// 不可变量return item.key
}// 返回缓存数据
func (item *CacheItem) Data() interface{} {// 不可变量return item.data
}

分析cachetable.go代码

package cache2gorwimport ("fmt""log""sync""time"
)type CacheTable struct {sync.RWMutex// 一个表名name string// 全部缓存项items map[interface{}]*CacheItem// 清理时间触发cleanupTimer *time.Timer// 清理时间间隔cleanupInterval time.Durationlogger *log.Logger// 尝试不存在key的时候回调的方法loadData func(key interface{}, args ...interface{}) *CacheItem// 加入缓存时回调方法addedItem []func(item *CacheItem)// 删除缓存时回调方aboutToDeleteItem []func(item *CacheItem)
}// 返回当前缓存表缓存长度
func (table *CacheTable) Count() int {table.RLock()defer table.RUnlock()return len(table.items)
}// 循环当前表的所有缓存
func (table *CacheTable) Foreach(trans func(key interface{}, item *CacheItem)) {table.RLock()defer table.RUnlock()for k, v := range table.items {trans(k, v)}
}func (table *CacheTable) SetDataLoader(f func(interface{}, ...interface{}) *CacheItem) {table.Lock()defer table.Unlock()table.loadData = f
}func (table *CacheTable) SetAddedItemCallback(f func(*CacheItem)) {if len(table.addedItem) > 0 {table.RemoveAddedItemCallbacks()}table.Lock()defer table.Unlock()table.addedItem = append(table.addedItem, f)
}func (table *CacheTable) RemoveAddedItemCallbacks() {table.Lock()defer table.Unlock()table.addedItem = nil
}// 加入缓存
func (table *CacheTable) Add(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {// 创建一个缓存对象item := NewCacheItem(key, lifeSpan, data)// 将缓存对象加入缓存表table.Lock()// 加入缓存内部方法table.addInternal(item)return item
}func (table *CacheTable) addInternal(item *CacheItem) {// 注意:执行这个方法之前,table一定要上锁// 执行这个方法会先解锁table.log("Adding item with key", item.key, "and lifespan of", item.lifeSpan, "to table", table.name)// 对象被放入map集合中table.items[item.key] = itemexpDur := table.cleanupIntervaladdedItem := table.addedItemtable.Unlock()// 执行回调方法if addedItem != nil {for _, callback := range addedItem {callback(item)}}// 有过期的缓存 和 清除时间间隔=0或存活时间<清除时间间隔if item.lifeSpan > 0 && (expDur == 0 || item.lifeSpan < expDur) {table.expirationCheck()}
}// 检查过期缓存
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)}now := time.Now()smallestDuration := 0 * time.Secondfor key, item := range table.items {item.RLock()lifeSpan := item.lifeSpanaccessedOn := item.accessedOnitem.RUnlock()// 缓存lifeSpan=0是无有效期,直接跳过if lifeSpan == 0 {continue}// 现在时间-最后访问时间 >= 存活时间 >>> 说明已过期if now.Sub(accessedOn) >= lifeSpan {fmt.Printf("[%s] check and delete expire key:%s\n", time.Now(), key)// 缓存需要删除table.deleteInternal(item)} else {// 计算出最短时间if smallestDuration == 0 || lifeSpan-now.Sub(accessedOn) < smallestDuration {smallestDuration = lifeSpan - now.Sub(accessedOn)}}// 以计算出最短时间设置下一次执行清除任务的时间间隔table.cleanupInterval = smallestDurationif smallestDuration > 0 {table.cleanupTimer = time.AfterFunc(smallestDuration, func() {// 异步执行检查过期go table.expirationCheck()})}table.Unlock()}}func (table *CacheTable) deleteInternal(key interface{}) (*CacheItem, error) {// 检查是否有这个缓存r, ok := table.items[key]if !ok {return nil, ErrKeyNotFound}aboutToDeleteItem := table.aboutToDeleteItemtable.Unlock()if aboutToDeleteItem != nil {for _, callback := range aboutToDeleteItem {callback(r)}}r.RLock()defer r.RUnlock()if r.aboutToExpire != nil {for _, callback := range aboutToDeleteItem {callback(r)}}table.Lock()table.log("Deleting item with key", key, "created on", r.createdOn, "and hit", r.accessCount, "times from table", table.name)// 删除delete(table.items, key)return r, nil
}// 删除一个缓存
func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) {table.Lock()defer table.Unlock()return table.deleteInternal(key)
}// 判断是否存在此缓存
func (table *CacheTable) Exists(key interface{}) bool {table.RLock()defer table.RUnlock()_, ok := table.items[key]return ok
}// 获取缓存
func (table *CacheTable) Value(key interface{}, args ...interface{}) (*CacheItem, error) {table.RLock()r, ok := table.items[key]loadData := table.loadDatatable.RUnlock()if ok {// 存在缓存,更新存活时间并返回r.KeepAlive()return r, nil}// 如果在缓存map无法找到缓存,则在数据上传器里面获取if loadData != nil {item := loadData(key, args...)if item != nil {table.Add(key, item.lifeSpan, item.data)return item, nil}}// 如果map和loadData都为空,则无此缓存return nil, ErrKeyNotFound
}// 内部log方法
func (table *CacheTable) log(v ...interface{}) {if table.logger == nil {return}table.logger.Println(v...)
}

如何并发安全

// 加入缓存
func (table *CacheTable) Add(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {// 创建一个缓存对象item := NewCacheItem(key, lifeSpan, data)// 将缓存对象加入缓存表table.Lock()// 加入缓存内部方法table.addInternal(item)return item
}func (table *CacheTable) addInternal(item *CacheItem) {// 注意:执行这个方法之前,table一定要上锁// 执行这个方法会先解锁table.log("Adding item with key", item.key, "and lifespan of", item.lifeSpan, "to table", table.name)// 对象被放入map集合中table.items[item.key] = itemexpDur := table.cleanupIntervaladdedItem := table.addedItemtable.Unlock()// 执行回调方法if addedItem != nil {for _, callback := range addedItem {callback(item)}}// 有过期的缓存 和 清除时间间隔=0或存活时间<清除时间间隔if item.lifeSpan > 0 && (expDur == 0 || item.lifeSpan < expDur) {table.expirationCheck()}
}

可以从添加缓存的代码中分析,在进行Add的时候,会进行一次加锁table.Lock(),在将缓存放入集合后,再table.Unlock(),而这个是对一个缓存表进行加锁,不同的缓存表是可以同时操作的。

同一个缓存表是会并发安全。

如何具有过期功能

在Add缓存

  1. 会进行一次缓存检查,如果到期还进行清除掉
  2. 还没有到期的,遍历会计算出一个最短时间,并生成一个定时任务,定时清除

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com