参考
启航 - cache2go源码分析
cache2go - cachetable源码分析
cache2go源码最后一讲 - examples
这3篇博客写得很详细,下面只做部分重点摘抄
一、简介https://github.com/muesli/cache2go
这是一个在github上开源的项目,原作者这样介绍:
Concurrency-safe golang caching library with expiration capabilities.
看懂了吗?简单说就是有心跳机制的并发安全的go语言缓存库。ok,下面我们要分析的这个项目是一个缓存库,并且有2大特性,并发安全和心跳机制!
二、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,可以参考
Golang 学习笔记四 结构体
Golang 学习笔记十 并发编程 锁
注意这个开源项目使用读锁时,经常是用临时变量缓存一下,然后就解锁了。
2.lifeSpan time.Duration 这个是寿命,从后面可知,如果为0表示无限寿命
3.然后就是创建时间,最后访问时间,访问次数,被删除时触发的回调。
4.在Golang 学习笔记六 函数和方法的区别介绍了函数和方法。
CacheItem就一个NewCacheItem()函数,设置了一些默认值。
CacheItem的方法定义,一共8个
看下最后一个:
// 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
}
注意这里锁的二次检查
有疑问加站长微信联系(非本文作者)