内容简介:参考
参考
启航 - cache2 go 源码分析
这3篇博客写得很详细,下面只做部分重点摘抄
一、简介 https://github.com/muesli/cache2go
这是一个在github上开源的项目,原作者这样介绍:
Concurrency-safe golang caching library with expiration capabilities.
看懂了吗?简单说就是有心跳机制的并发安全的go语言缓存库。ok,下面我们要分析的这个项目是一个缓存库,并且有2大特性,并发安全和心跳机制!
image.png
二、CacheItem
type CacheItem struct { sync.RWMutex // The item's key. key interface{} // The item's data. data interface{} // How long will the item live in the cache when not being accessed/kept alive. lifeSpan time.Duration // Creation timestamp. createdOn time.Time // Last access timestamp. accessedOn time.Time // How often the item was accessed. accessCount int64 // Callback method triggered right before removing the item from the cache aboutToExpire func(key interface{}) }
1.结构体中使用了匿名的读写锁sync.RWMutex,可以参考
注意这个开源项目使用读锁时,经常是用临时变量缓存一下,然后就解锁了。
2.lifeSpan time.Duration 这个是寿命,从后面可知,如果为0表示无限寿命
3.然后就是创建时间,最后访问时间,访问次数,被删除时触发的回调。
4.在 Golang 学习笔记六 函数和方法的区别 介绍了函数和方法。
CacheItem就一个NewCacheItem()函数,设置了一些默认值。
CacheItem的方法定义,一共8个
image.png
看下最后一个:
// SetAboutToExpireCallback configures a callback, which will be called right // before the item is about to be removed from the cache. func (item *CacheItem) SetAboutToExpireCallback(f func(interface{})) { item.Lock() defer item.Unlock() item.aboutToExpire = f }
这就是在设置那个回调属性了,这个方法的形参是f func(interface{}),也就是说形参名为f,形参类型是func(interface{}),这是一个函数类型,这个函数类型的参数是一个interface{},也就是空接口,因为任意类型都可以被认为实现了空接口,所以这里可以接收任意类型的实参。也就是说f的类型是一个可以接收任意类型参数的函数类型。
在callbacks.go的例子中,显示了使用方式:
// Caching a new item that expires in 3 seconds res = cache.Add("anotherKey", 3*time.Second, "This is another test") // This callback will be triggered when the item is about to expire res.SetAboutToExpireCallback(func(key interface{}) { fmt.Println("About to expire:", key.(string)) })
设置了寿命是3秒钟,运行后也能看到,3秒后自己失效了,打印出 About to expire: anotherKey
注意 func (item *CacheItem) SetAboutToExpireCallback(f func(interface{})) {
的参数里,是没有形参名称的,因为不需要,看个更简单的例子:
func main() { var show func(int) show = func(num int) { fmt.Println(num) } show(123) }
另外,在使用示例中,对key使用了类型断言,是因为示例使用了字符串做key。实际上key是个接口类型,可以是任意类型。
在cachetable.go中,deleteInternal方法中会调用item的aboutToExpire
if r.aboutToExpire != nil { r.aboutToExpire(key) }
三、cachetable.go
1.Add和NotFoundAdd方法
NotFoundAdd会根据key判断是不是已经有数据了,如果没有的话,也会添加。两者都会调用addInternal。
2.addInternal
expDur := table.cleanupInterval // 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() }
lifeSpan如果为0,相当于无限寿命,不会触发过期时间的检查。lifeSpan大于0时,这个cleanupInterval第一次使用时,默认是0,也就是还没设置检查时间间隔。那么会立即触发时间检查。而如果已经有检查时间间隔,则看一下新添加的item如果寿命小于这个间隔,也要立即触发检查。
3.expirationCheck
开始检查时,如果有cleanupTimer,要先停掉。
然后就是遍历一下items,逐项检查。有过期的,就调用table.deleteInternal(key)。没过期的,就计算一下还差多久要过期,然后使用smallestDuration记录一下最小的那个快过期时间,作为下次的检查时间。
如果下次检查时间大于0,则启动一个cleanupTimer,到时间后,重新执行expirationCheck
// Setup the interval for the next cleanup run. table.cleanupInterval = smallestDuration if smallestDuration > 0 { table.cleanupTimer = time.AfterFunc(smallestDuration, func() { go table.expirationCheck() }) }
这里有go关键字,并不是循环启动goroutine,启动一个新的goroutine后当前goroutine会退出,这里不会引起goroutine泄漏。
4.func (table CacheTable) Value(key interface{}, args ...interface{}) ( CacheItem, error) {
这里的args是在loadData != nil时,给loadData使用的。
if loadData != nil { item := loadData(key, args...) if item != nil { table.Add(key, item.lifeSpan, item.data) return item, nil } return nil, ErrKeyNotFoundOrLoadable }
从dataloader.go示例中,可以看出,loadData可以在缓存中没有数据中,从数据库、网络、文件中读取。
5.清空
// Flush deletes all items from this cache table. func (table *CacheTable) Flush() { table.Lock() defer table.Unlock() table.log("Flushing table", table.name) table.items = make(map[interface{}]*CacheItem) table.cleanupInterval = 0 if table.cleanupTimer != nil { table.cleanupTimer.Stop() } }
从注释可以看出来这个函数就是清空数据的作用,实现方式简单粗暴,让table的items属性指向一个新建的空map,cleanup操作对应的时间间隔设置为0,并且计时器停止。这里也可以得到cleanupInterval为0是什么场景,也就是说0不是代表清空操作死循环,间隔0秒就执行,而是表示不需要操作,缓存表还是空的。
6.排序,涉及到sort.Sort的玩法
1// MostAccessed returns the most accessed items in this cache table 2//【访问频率高的count条item全部返回】 3func (table *CacheTable) MostAccessed(count int64) []*CacheItem { 4 table.RLock() 5 defer table.RUnlock() 6 //【这里的CacheItemPairList是[]CacheItemPair类型,是类型不是实例】 7 //【所以p是长度为len(table.items)的一个CacheItemPair类型的切片类型 8 p := make(CacheItemPairList, len(table.items)) 9 i := 0 10 //【遍历items,将Key和AccessCount构造成CacheItemPair类型数据存入p切片】 11 for k, v := range table.items { 12 p[i] = CacheItemPair{k, v.accessCount} 13 i++ 14 } 15 //【这里可以直接使用Sort方法来 排序 是因为CacheItemPairList //实现了sort.Interface接口,也就是Swap,Len,Less三个方法】 16 //【但是需要留意上面的Less方法在定义的时候把逻辑倒过来了,导致排序是从大到小的】 17 sort.Sort(p) 18 19 var r []*CacheItem 20 c := int64(0) 21 for _, v := range p { 22 //【控制返回值数目】 23 if c >= count { 24 break 25 } 26 27 item, ok := table.items[v.Key] 28 if ok { 29 //【因为数据是按照访问频率从高到底排序的,所以可以从第一条数据开始加】 30 r = append(r, item) 31 } 32 c++ 33 } 34 35 return r 36}
四、cache.go
func Cache(table string) *CacheTable { mutex.RLock() t, ok := cache[table] mutex.RUnlock() if !ok { mutex.Lock() t, ok = cache[table] // Double check whether the table exists or not. if !ok { t = &CacheTable{ name: table, items: make(map[interface{}]*CacheItem), } cache[table] = t } mutex.Unlock() } return t }
注意这里锁的二次检查
以上所述就是小编给大家介绍的《Golang 开源项目cache2go 解读》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 阿里 Blink 正式开源,重要优化点解读
- 阿里 Blink 正式开源,重要优化点解读
- 解读Bento Android框架的开源细节
- 匠心独运解读Mybatis源码,纯手工打造开源框架
- 从技术上解读大数据的应用现状和开源未来
- 解读 2018:13 家开源框架谁能统一流计算?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
共鸣:内容运营方法论
舒扬 / 机械工业出版社 / 2017-5-8 / 59.00
近5年来网络信息量增长了近10倍,信息极度过剩。移动互联网以碎片化、强黏度以及惊人的覆盖率给传统的商业环境带来了巨大的影响,向陈旧的广告、公关、媒体行业展开了深度的冲击。 传统的以渠道为中心的传播思想几近失效,优秀内容成为了各行业最稀缺的资产,这是时代赋予内容生产者的巨大机会。本书作者在多年经验和大量案例研究的基础上,总结出了移动互联网时代的内容运营方法论——共鸣,它将告诉我们如何收获核心粉......一起来看看 《共鸣:内容运营方法论》 这本书的介绍吧!