简介
项目源码: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 <muesli@gmail.com>
*
* 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
}
有疑问加站长微信联系(非本文作者)