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{})
}
View Code

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)
}
View Code

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()
}
View Code

 

    • 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()
    }
}
View Code

 

    • 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
}
View Code

5. 参考原文

posted @ 2019-06-13 12:16  西凉风雷  阅读(125)  评论(0编辑  收藏  举报
要看看 订阅 么?