美文网首页
Golang 开源项目cache2go 解读

Golang 开源项目cache2go 解读

作者: 合肥黑 | 来源:发表于2019-03-11 16:17 被阅读0次

    参考
    启航 - cache2go源码分析
    cache2go - cachetable源码分析
    cache2go源码最后一讲 - examples
    这3篇博客写得很详细,下面只做部分重点摘抄

    一、简介https://github.com/muesli/cache2go

    这是一个在github上开源的项目,原作者这样介绍:

    Concurrency-safe golang caching library with expiration capabilities.

    看懂了吗?简单说就是有心跳机制的并发安全的go语言缓存库。ok,下面我们要分析的这个项目是一个缓存库,并且有2大特性,并发安全和心跳机制!


    image.png
    二、CacheItem
    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.结构体中使用了匿名的读写锁sync.RWMutex,可以参考
    Golang 学习笔记四 结构体
    Golang 学习笔记十 并发编程 锁
    注意这个开源项目使用读锁时,经常是用临时变量缓存一下,然后就解锁了。

    2.lifeSpan time.Duration 这个是寿命,从后面可知,如果为0表示无限寿命

    3.然后就是创建时间,最后访问时间,访问次数,被删除时触发的回调。

    4.在Golang 学习笔记六 函数和方法的区别介绍了函数和方法。
    CacheItem就一个NewCacheItem()函数,设置了一些默认值。
    CacheItem的方法定义,一共8个

    image.png
    看下最后一个:
    // SetAboutToExpireCallback configures a callback, which will be called right
    // before the item is about to be removed from the cache.
    func (item *CacheItem) SetAboutToExpireCallback(f func(interface{})) {
        item.Lock()
        defer item.Unlock()
        item.aboutToExpire = f
    }
    

    这就是在设置那个回调属性了,这个方法的形参是f func(interface{}),也就是说形参名为f,形参类型是func(interface{}),这是一个函数类型,这个函数类型的参数是一个interface{},也就是空接口,因为任意类型都可以被认为实现了空接口,所以这里可以接收任意类型的实参。也就是说f的类型是一个可以接收任意类型参数的函数类型。

    在callbacks.go的例子中,显示了使用方式:

    // Caching a new item that expires in 3 seconds
    res = cache.Add("anotherKey", 3*time.Second, "This is another test")
    // This callback will be triggered when the item is about to expire
    res.SetAboutToExpireCallback(func(key interface{}) {
        fmt.Println("About to expire:", key.(string))
    })
    

    设置了寿命是3秒钟,运行后也能看到,3秒后自己失效了,打印出About to expire: anotherKey

    注意func (item *CacheItem) SetAboutToExpireCallback(f func(interface{})) {的参数里,是没有形参名称的,因为不需要,看个更简单的例子:

    func main() {
        var show func(int)
        show = func(num int) { fmt.Println(num) }
        show(123)
    }
    

    另外,在使用示例中,对key使用了类型断言,是因为示例使用了字符串做key。实际上key是个接口类型,可以是任意类型。

    在cachetable.go中,deleteInternal方法中会调用item的aboutToExpire

        if r.aboutToExpire != nil {
            r.aboutToExpire(key)
        }
    
    三、cachetable.go

    1.Add和NotFoundAdd方法
    NotFoundAdd会根据key判断是不是已经有数据了,如果没有的话,也会添加。两者都会调用addInternal。

    2.addInternal

    expDur := table.cleanupInterval
    // 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()
    }
    

    lifeSpan如果为0,相当于无限寿命,不会触发过期时间的检查。lifeSpan大于0时,这个cleanupInterval第一次使用时,默认是0,也就是还没设置检查时间间隔。那么会立即触发时间检查。而如果已经有检查时间间隔,则看一下新添加的item如果寿命小于这个间隔,也要立即触发检查。

    3.expirationCheck
    开始检查时,如果有cleanupTimer,要先停掉。

    然后就是遍历一下items,逐项检查。有过期的,就调用table.deleteInternal(key)。没过期的,就计算一下还差多久要过期,然后使用smallestDuration记录一下最小的那个快过期时间,作为下次的检查时间。

    如果下次检查时间大于0,则启动一个cleanupTimer,到时间后,重新执行expirationCheck

    // Setup the interval for the next cleanup run.
    table.cleanupInterval = smallestDuration
    if smallestDuration > 0 {
        table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
            go table.expirationCheck()
        })
    }
    

    这里有go关键字,并不是循环启动goroutine,启动一个新的goroutine后当前goroutine会退出,这里不会引起goroutine泄漏。

    4.func (table CacheTable) Value(key interface{}, args ...interface{}) (CacheItem, error) {
    这里的args是在loadData != nil时,给loadData使用的。

    if loadData != nil {
        item := loadData(key, args...)
        if item != nil {
            table.Add(key, item.lifeSpan, item.data)
            return item, nil
        }
    
        return nil, ErrKeyNotFoundOrLoadable
    }
    

    从dataloader.go示例中,可以看出,loadData可以在缓存中没有数据中,从数据库、网络、文件中读取。
    5.清空

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

    从注释可以看出来这个函数就是清空数据的作用,实现方式简单粗暴,让table的items属性指向一个新建的空map,cleanup操作对应的时间间隔设置为0,并且计时器停止。这里也可以得到cleanupInterval为0是什么场景,也就是说0不是代表清空操作死循环,间隔0秒就执行,而是表示不需要操作,缓存表还是空的。

    6.排序,涉及到sort.Sort的玩法

     1// MostAccessed returns the most accessed items in this cache table
     2//【访问频率高的count条item全部返回】
     3func (table *CacheTable) MostAccessed(count int64) []*CacheItem {
     4    table.RLock()
     5    defer table.RUnlock()
     6    //【这里的CacheItemPairList是[]CacheItemPair类型,是类型不是实例】
     7    //【所以p是长度为len(table.items)的一个CacheItemPair类型的切片类型
     8    p := make(CacheItemPairList, len(table.items))
     9    i := 0
    10    //【遍历items,将Key和AccessCount构造成CacheItemPair类型数据存入p切片】
    11    for k, v := range table.items {
    12        p[i] = CacheItemPair{k, v.accessCount}
    13        i++
    14    }
    15    //【这里可以直接使用Sort方法来排序是因为CacheItemPairList
            //实现了sort.Interface接口,也就是Swap,Len,Less三个方法】
    16    //【但是需要留意上面的Less方法在定义的时候把逻辑倒过来了,导致排序是从大到小的】
    17    sort.Sort(p)
    18
    19    var r []*CacheItem
    20    c := int64(0)
    21    for _, v := range p {
    22        //【控制返回值数目】
    23        if c >= count {
    24            break
    25        }
    26
    27        item, ok := table.items[v.Key]
    28        if ok {
    29            //【因为数据是按照访问频率从高到底排序的,所以可以从第一条数据开始加】
    30            r = append(r, item)
    31        }
    32        c++
    33    }
    34
    35    return r
    36}
    
    四、cache.go
    func Cache(table string) *CacheTable {
        mutex.RLock()
        t, ok := cache[table]
        mutex.RUnlock()
    
        if !ok {
            mutex.Lock()
            t, ok = cache[table]
            // Double check whether the table exists or not.
            if !ok {
                t = &CacheTable{
                    name:  table,
                    items: make(map[interface{}]*CacheItem),
                }
                cache[table] = t
            }
            mutex.Unlock()
        }
    
        return t
    }
    

    注意这里锁的二次检查

    相关文章

      网友评论

          本文标题:Golang 开源项目cache2go 解读

          本文链接:https://www.haomeiwen.com/subject/iiazuqtx.html