Kubernetes 学习(六)Kubernetes 源码阅读之准备篇------Cache2go 源码分析[转载]
0. 前言
1. Cache2go 是什么
-
Cache2go:有心跳机制的并发安全的单机版 golang 内存数据库
2. 项目结构
- 功能实现相关源码文件主要有 3 个:cache.go、cacheitem.go、cachetable.go
3. 关键数据结构
-
项目中涉及主要数据类型如下:
- CacheItem:缓存表项
- CacheTable:缓存表
3.1 CacheItem
- CacheItem 类型是用来表示一个单独的缓存条目:
// CacheItem 是一个单独的缓存条目 // 参数 data 包含用户设置在缓存里的值. type CacheItem struct { sync.RWMutex // 该项的键 key interface{} // 该项的值 data interface{} // 不能被访问后的存活时间 lifeSpan time.Duration // 被创建的时间戳 createdOn time.Time // 上一次被访问的时间戳 accessedOn time.Time // 多久被访问一次 accessCount int64 // 被删除时触发的回调方法 aboutToExpire []func(key interface{}) }
3.2 CacheTable
- CacheTable 描述了缓存中的一个表项,里面的 items 属性就是上面讲到的 CacheItem 类型实例:
// CacheTable 是缓存中的一个表 type CacheTable struct { sync.RWMutex // 表的名称 name string // 所有的缓存项 items map[interface{}]*CacheItem // 负责触发清除操作的计时器 cleanupTimer *time.Timer // 当前清除操作触发的时间间隔 cleanupInterval time.Duration // The logger used for this table. logger *log.Logger // 需要提取一个不存在的key时触发的回调函数 loadData func(key interface{}, args ...interface{}) *CacheItem // 增加一个缓存条目时触发的回调函数 addedItem []func(item *CacheItem) // 从缓存中删除条目前触发的回调函数 aboutToDeleteItem []func(item *CacheItem) }
4. 代码逻辑
4.1 CacheItem
- 主要方法如下:
- 源码中的读写操作都加了相应的读写锁,保证线程安全
4.2 CacheTable
- cachetable.go 还包含 CacheItemPair、CacheItemPairList,分别记录每个 key 次数和构成 CacheItemPair 的 slice
- 主要方法如下:
- Count():返回的是指定 CacheTable 中的 item 条目数
- Foreach():接收一个函数参数 trans,方法内遍历了 items,把每个 key 和 value 都传给了 trans 函数来处理
- SetDataLoader():访问一个不存在的 key 时会调用到上述 CacheTable.loadData 方法,这个方法通过 SetDataLoader 设定,方法的实现由用户来自定义
- SetAddedItemCallback():当添加一个 item 到缓存表中时会被调用一批方法,绑定到 CacheTable.addedItem 上。当添加一个 CacheItem 的时候,同时会调用这些回调函数,这个函数可以选择对 CacheItem 做一些处理,比如打日志等。SetAddedItemCallback() 每次删除过去的方法,并添加新的方法
- SetAboutToDeleteItemCallback():删除一个 item 时同样会调用一批方法,和添加类似
- SetLogger():设置 CacheTable.logger 属性
- expirationCheck():做一个定期的数据过期检查操作,异步执行新的一轮检查
// Expiration check loop, triggered by a self-adjusting timer. func (table *CacheTable) expirationCheck() { table.Lock() // 计时器暂停 if table.cleanupTimer != nil { table.cleanupTimer.Stop() } if table.cleanupInterval > 0 { table.log("Expiration check triggered after", table.cleanupInterval, "for table", table.name) } else { table.log("Expiration check installed for table", table.name) } // To be more accurate with timers, we would need to update 'now' on every // loop iteration. Not sure it's really efficient though. now := time.Now() smallestDuration := 0 * time.Second for key, item := range table.items { // Cache values so we don't keep blocking the mutex. item.RLock() lifeSpan := item.lifeSpan accessedOn := item.accessedOn item.RUnlock() // 存活时间为 0 表示一直存活 if lifeSpan == 0 { continue } // 计算当前时间和上一次访问的时间差是否超过存活时间 if now.Sub(accessedOn) >= lifeSpan { // 过期即可删除 table.deleteInternal(key) } else { // 未超期则计算最接近过期时间的条目还差多久过期 if smallestDuration == 0 || lifeSpan-now.Sub(accessedOn) < smallestDuration { smallestDuration = lifeSpan - now.Sub(accessedOn) } } } // Setup the interval for the next cleanup run. table.cleanupInterval = smallestDuration if smallestDuration > 0 { // 如果均不需要过期则无需执行后续检查,否则在最小时间间隔后异步再次执行检查 table.cleanupTimer = time.AfterFunc(smallestDuration, func() { go table.expirationCheck() }) } table.Unlock() }
-
- Add():添加一个 key/value 对到 cache 并指定存活时间。NewCacheItem 函数创建 CacheItem 类型实例,返回 *CacheItem 类型,然后将指针传给了 addInternal 方法,然后返回了该指针
- addInternal():将 CacheItem 类型的实例添加到 CacheTable 中,调用这个方法前需要加锁,将 table.cleanupInterval 和 table.addedItem 保存到局部变量,紧接着释放了锁。然后调用所有添加 item 触发的回调函数。然后触发检查条件:
- 第一个条件 item.lifeSpan > 0 指当前 item 设置了存活时间
- 然后 && (expDur == 0 || item.lifeSpan < expDur),expDur 保存的是 table.cleanupInterval,这个值为 0 也就是还没有设置检查时间间隔
- 或者 item.lifeSpan < expDur 也就是设置了,但是当前新增的 item 的 lifeSpan 要更小,这个时候就触发 expirationCheck 执行
- 这里可能有点绕,要注意 lifeSpan 是一个 item 的存活时间,而 cleanupInterval 是对于一个 table 来说触发检查还剩余的时间,如果前者更小,那么就说明需要提前出发 check 操作了
func (table *CacheTable) addInternal(item *CacheItem) { // Careful: do not run this method unless the table-mutex is locked! // It will unlock it for the caller before running the callbacks and checks table.log("Adding item with key", item.key, "and lifespan of", item.lifeSpan, "to table", table.name) table.items[item.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. if addedItem != nil { for _, callback := range addedItem { callback(item) } } // 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() } }
-
- NotFoundAdd():开始时判断是检查 items 中是否有这个 key,存在则返回 false;不存在时创建一个 CacheItem 类型的实例,然后调用 addInternal 添加 item,最后返回 true
- Delete():加锁,调用 deleteInternal 方法,然后释放锁
- deleteInternal():首先判断 key 是否存在,不存在则返回并报错;加锁执行表项删除触发的所有回调方法,然后加锁从表中删除该项
- Exists():判断某个 key 是否存在
- Value():加锁保存 loadData 方法,对于存在的 key,更新上一次访问时间返回对应值;不存在的 key 执行 loadData 方法,对于返回的 item 则返回对应的值,否则返回空并报错
- Flush():主要是清空数据,让 table 的 items 属性指向一个新建的空 map,cleanup 操作对应的时间间隔设置为 0,并且计时器停止(这里也可以得到 cleanupInterval 为 0 是什么场景,0 不是代表清空操作死循环,间隔 0 秒就执行,而是表示不需要操作,缓存表还是空的)
- MostAccessed(count int64):返回最多访问的 count 个数
4.3 cache.go
// 表存在则返回表,否则则创建一个表再返回 func Cache(table string) *CacheTable { mutex.RLock() t, ok := cache[table] mutex.RUnlock() if !ok { mutex.Lock() t, ok = cache[table] // 表不存在时需要创建一个空表,二次加锁检查为了并发安全 if !ok { t = &CacheTable{ name: table, items: make(map[interface{}]*CacheItem), } cache[table] = t } mutex.Unlock() } return t }