Cache2go源码分析

Delete接口

现在,我们来看下从 CacheTable 里面删除一个 CacheItem 的接口,即 Delete 接口,具体代码如下:

// Delete an item from the cache. func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) { table.Lock() defer table.Unlock() return table.deleteInternal(key) }

该接口传入一个我们需要删除的 key,返回被删除的对象,在函数内,我们首先,对整个 table 进行加锁,最后,使用 defer 对整个 table 进行解锁,具体的删除操作,调用的是 deleteInternal 函数,deleteInternal 函数的具体实现如下:

func (table *CacheTable) deleteInternal(key interface{}) (*CacheItem, error) { r, ok := table.items[key] if !ok { return nil, ErrKeyNotFound } // Cache value so we don't keep blocking the mutex. aboutToDeleteItem := table.aboutToDeleteItem table.Unlock() // Trigger callbacks before deleting an item from cache. if aboutToDeleteItem != nil { aboutToDeleteItem(r) } r.RLock() defer r.RUnlock() if r.aboutToExpire != nil { r.aboutToExpire(key) } 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 }

在该函数里,首先,判断要删除的键是否存在,如果键不存在,则直接返回。接着,是同样的操作,先获取要操作的对象,并立即释放锁。也就是,如果 CacheTable 里有删除的回调,那么首先调用删除的回调。

接着,判断 CacheItem 对象是否有删除的回调,如果有,则调用,最后,直接调用 delete 函数删除 map 中的元素即可。

Count接口

Count 接口,具体代码如下:

func (table *CacheTable) Count() int { table.RLock() defer table.RUnlock() return len(table.items) }

该接口非常简单,直接返回 map 的长度即可,注意,操作同样需要加锁。

Foreach接口

Foreach 接口,用于遍历 CacheTable 里面的每一个 Item,并调用传入的遍历函数,具体代码如下:

// Foreach all 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) } }

该接口非常简单,操作同样需要加锁。

Exists接口

Exists 接口,用于判断 CacheTable 里面是否存在某个键的 Item,具体代码如下:

// Exists returns whether an item exists in the cache. Unlike the Value method // Exists neither tries to fetch data via the loadData callback nor does it // keep the item alive in the cache. func (table *CacheTable) Exists(key interface{}) bool { table.RLock() defer table.RUnlock() _, ok := table.items[key] return ok }

直接使用 map 进行判断即可。

NotFoundAdd接口

NotFoundAdd 接口用于如果在 CacheTable 里面不存在某个键,则添加,存在,则不添加,具体代码如下:

// NotFoundAdd tests whether an item not found in the cache. Unlike the Exists // method this also adds data if they key could not be found. func (table *CacheTable) NotFoundAdd(key interface{}, lifeSpan time.Duration, data interface{}) bool { table.Lock() if _, ok := table.items[key]; ok { table.Unlock() return false } item := NewCacheItem(key, lifeSpan, data) table.addInternal(item) return true }

该函数首先判断键是否存在,如果存在,则直接返回,否则,创建一个新的 CacheItem,然后调用 addInternal 添加进去即可。

Value接口

Value 接口用于根据 key 返回缓存中对应的 value ,类型为 *CacheItem 。如果 key 存在,则同时更新它的访问计数和最后访问时间;如果不存在,则尝试调用事先注册的回调函数 loadData 初始化数据到缓存表。具体代码如下:

// Value returns an item from the cache and marks it to be kept alive. You can // pass additional arguments to your DataLoader callback function. func (table *CacheTable) Value(key interface{}, args ...interface{}) (*CacheItem, error) { table.RLock() r, ok := table.items[key] loadData := table.loadData table.RUnlock() if ok { // Update access counter and timestamp. r.KeepAlive() return r, nil } // Item doesn't exist in cache. Try and fetch it with a data-loader. if loadData != nil { item := loadData(key, args...) if item != nil { table.Add(key, item.lifeSpan, item.data) return item, nil } return nil, ErrKeyNotFoundOrLoadable } return nil, ErrKeyNotFound }

同样,首先加锁,获取要操作的数据并缓存,接着,立即解锁。如果键存在,则直接调用 KeepAlive 方法,更新其访问时间,并直接返回获取到的对象。

如果不存在,并且 CacheTable 的 loadData 回调不为空,则直接调用 loadData 回调,loadData 回调的第二个参数是一个可变参数,回调之后,再次将 CacheItem 添加到 CacheTable 中并返回。

Flush接口

Flush 接口用于删除 CacheTable 中的所有的 CacheItem 对象,具体代码如下:

// 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() } }

直接将 items 设置为空并且停止定时器即可。

MostAccessed接口

MostAccessed 接口按照访问次数返回 CacheItem 的数组,具体代码如下:

// CacheItemPair maps key to access counter type CacheItemPair struct { Key interface{} AccessCount int64 } // CacheItemPairList is a slice of CacheIemPairs that implements sort. // Interface to sort by AccessCount. type CacheItemPairList []CacheItemPair func (p CacheItemPairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p CacheItemPairList) Len() int { return len(p) } func (p CacheItemPairList) Less(i, j int) bool { return p[i].AccessCount > p[j].AccessCount } // MostAccessed returns the most accessed items in this cache table func (table *CacheTable) MostAccessed(count int64) []*CacheItem { table.RLock() defer table.RUnlock() p := make(CacheItemPairList, len(table.items)) i := 0 for k, v := range table.items { p[i] = CacheItemPair{k, v.accessCount} i++ } sort.Sort(p) var r []*CacheItem c := int64(0) for _, v := range p { if c >= count { break } item, ok := table.items[v.Key] if ok { r = append(r, item) } c++ } return r }

我们看到,首先,定义了一个 CacheItemPair 结构体,该结构体包含一个接口类型的 Key 和一个 int64 类型的 AccessCount 访问次数,接着,我们重写了该结构的 Swap、Len 和 Less 方法,用于排序。

接着,我们使用 make 创建了跟 CacheTable 里面的 items 一样长度的一个切片 p,并使用 for range 遍历 CacheTable 的所有的 items,组装成 CacheItemPair 对象,并添加到切片中,最后,直接调用 sort.Sort 传入该切片即可实现排序,因为上面我们已经重新了三个方法。

最后,我们再次遍历 p 切片,并根据 p 切片里的键获取 CacheTable 对象的 items 的具体值即可,并再次组装成新的切片返回。