cache2go源码阅读
2014 年 7 月 2 日
简介
项目源码: https://github.com/muesli/cac…
这个项目代码量很少,看完再模仿写一遍后觉得非常初学者。这也是我看的第一个go项目。对学习锁和并发有很大帮助,里面的很多代码姿势也值得去学习。
功能:
- 实现了并发安全
- 可以设置缓存的生命周期,删除,添加,访问数据时的回调函数。
- 可以记录缓存的最近访问,访问次数,创建时间。
- 可以自动执行过期缓存清理(里面的代码很值得去学习体会)
这个缓存库里边最主要的有三个文件cache.go,chchetable.go,cacheItem.go。这个代码分析是 分析0.1版本的源码
cache.go里定义并声明了一个缓存数据库。
chchetable.go里定义了缓存数据表。
cacheItem.go里定义的则是缓存数据表的条目。
调用关系为cache.go -> chchetable.go -> cacheItem.go
疑问:
- 在map中使用interface{},作者似乎没对interface{}底层数据为不能比较这种情况(比如是slice)做什么处理
- chchetable.go中的log用的比较诡异,在我看来弄成内置的是不是更省事呢?
- 有些单个函数里面频繁的加锁解锁(eg: 加只读锁,解只读锁->加互斥锁,解互斥锁)。是不是更加影响到性能,能不能直接加互斥锁,解互斥锁。
cacheItem.go
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.CacheItem的生成
func CreateCacheItem(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, } }
2.touch一下缓存,让其保持鲜活
func (item *CacheItem) KeepAlive() { item.Lock() defer item.Unlock() item.accessedOn = time.Now() item.accessCount++ }
chchetable.go
type CacheTable struct { //巧用内置匿名变量,这个类型有了读写锁的性质 sync.RWMutex // The table's name. // 表名 name string // All cached items. // 条目 items map[interface{}]*CacheItem // Timer responsible for triggering cleanup. // 用来触发方法expirationCheck cleanupTimer *time.Timer // Current timer duration. // 赋值给cleanupTimer的清理周期 cleanupInterval time.Duration // The logger used for this table. // 记录日志,怎不用成匿名变量呢? logger *log.Logger // Callback method triggered when trying to load a non-existing key. // 如果获取值不存在时调用 loadData func(key interface{}, args ...interface{}) *CacheItem // Callback method triggered when adding a new item to the cache. // 如果加入条目要触发什么 addedItem func(item *CacheItem) // Callback method triggered before deleting an item from the cache. // 如果删除条目要触发什么 aboutToDeleteItem func(item *CacheItem) }
下面列出重要的函数和方法
1.遍历表的所有条目,并根据提供的trans对条目进行解析
func (table *CacheTable) Foreach(trans func(key interface{}, item *CacheItem)) { table.RLock() defer table.RUnlock() for k, v := range table.items { // 翻译函数,map[interface{}]*CacheItem trans(k, v) } }
2.缓存条目的过期检查,可自我更新检查的频率。( 此源码的一个重要看点
)
func (table *CacheTable) expirationCheck() { table.Lock() if table.cleanupTimer != nil { // 不是nil就停止timer的执行 table.cleanupTimer.Stop() } if table.cleanupInterval > 0 { // 之前table.cleanupTimer是执行的。 // 能运行到这可能是因为cleanupTimer触发的, // 也可能是因为方法Add或方法NotFoundAdd table.log("Expiration check triggered after", table.cleanupInterval, "for table", table.name) } else { // 之前table.cleanupTimer是stop的,为表下载过期检查 // 能运行到这不可能是因为cleanupTimer触发的 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不该放到循环里面去每次更新一次吗? now := time.Now() //smallestDuration记录所有条目中剩余时间的最小值 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 { //等于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. // 更新smallestDuration 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 // 关键哦!!!!!不过感觉这里使用go的意义不大吧。可以省略吗 if smallestDuration > 0 { table.cleanupTimer = time.AfterFunc(smallestDuration, func() { go table.expirationCheck() }) } table.Unlock() }
3.添加条目
func (table *CacheTable) Add(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem { item := CreateCacheItem(key, lifeSpan, data) // Add item to cache. table.Lock() table.log("Adding item with key", key, "and lifespan of", lifeSpan, "to table", table.name) table.items[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. // 如果有添加条目时的触发器 // 问题:如果table中此时的addedItem被改了,将怎样, // 调用的是那个函数,这取决于函数类型是值类型还是引用类型 // 经过测试得出结论,其为值类型,改了还是运行原来的 if addedItem != nil { addedItem(&item) } // If we haven't set up any expiration check timer or found a more imminent item. if lifeSpan > 0 && (expDur == 0 || lifeSpan < expDur) { //在这里运行检测expirationCheck table.expirationCheck() } return &item }
4.删除条目
// Delete an item from the cache. func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) { // 看看是否存在 table.RLock() r, ok := table.items[key] if !ok { table.RUnlock() return nil, ErrKeyNotFound } // Cache value so we don't keep blocking the mutex. aboutToDeleteItem := table.aboutToDeleteItem table.RUnlock() // Trigger callbacks before deleting an item from cache. // table有没有delete的触发器,有的话就调用吧 if aboutToDeleteItem != nil { aboutToDeleteItem(r) } r.RLock() defer r.RUnlock() // 看item有没有delete触发器。不太明白为什么两个触发器? if r.aboutToExpire != nil { r.aboutToExpire(key) } table.Lock() defer table.Unlock() 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 }
5.获取条目
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 { // 如果存在loadData这个触发器就运行之 item := loadData(key, args...) if item != nil { table.Add(key, item.lifeSpan, item.data) return item, nil } return nil, ErrKeyNotFoundOrLoadable } return nil, ErrKeyNotFound }
6.清空表
func (table *CacheTable) Flush() { table.Lock() defer table.Unlock() table.log("Flushing table", table.name) //重新生成一个空的,让gc去处理 table.items = make(map[interface{}]*CacheItem) table.cleanupInterval = 0 if table.cleanupTimer != nil { table.cleanupTimer.Stop() } }
根据访问次数排序
type CacheItemPair struct { Key interface{} AccessCount int64 } // 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 } // 最多访问获取 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 }
cache.go
/* * Simple caching library with expiration capabilities * Copyright (c) 2012, Radu Ioan Fericean * 2013, Christian Muehlhaeuser* * For license see LICENSE.txt */ package cache2go import ( "sync" ) var ( // 相当于数据库 cache = make(map[string]*CacheTable) mutex sync.RWMutex ) // Returns the existing cache table with given name or creates a new one // if the table does not exist yet. // 把这个做成方法会是怎样呢? func Cache(table string) *CacheTable { mutex.RLock() t, ok := cache[table] mutex.RUnlock() //如果cache map没有table这个key,创! if !ok { //课件CacneTable 两个最关键的变量就是如下 t = &CacheTable{ name: table, //表名 items: make(map[interface{}]*CacheItem), } //上锁并赋值 mutex.Lock() cache[table] = t mutex.Unlock() } return t }