go cache2go分析

该项目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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350