Golang 开源项目cache2go 解读

懒皮 · · 679 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

参考
启航 - 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 解读

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

679 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传