欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

CloudGeek读源码系列-cache2go源码解析(一)

程序员文章站 2022-03-30 10:09:05
一、cache2go是什么 作者说:Concurrency-safe golang caching library with expiration capabilities. 什么意思呢? 有心跳机制的并发安全的go语言缓存库 学习一门语言有一个很好的方法就是阅读优秀的开源项目源码,学习优秀前辈的编 ......

0、写在最前面

What:《cache2go的源码解析》会分为(一)(二)两讲,内容包括整个项目的所有功能代码和例子程序。完整的哦~

Why:如果你是一个go语言新手,在看下面的代码分析过程中你肯定会遇到一些自己陌生的编码方式、陌生的知识点,这个过程中我希望你遇到一个点掌握一个点,比如看到下面的代码用到了锁,就去找各种资料把锁相关的知识点学了。看到回调函数,就思考一下人家为什么这些地方都使用回调函数,有什么好处。这样在看完这个项目源码分析后,你就能学到一部分知识模块。《CloudGeek读源码系列》还会讲解好几个更大的项目,每个项目的源码阅读过程中都会学习到不同的知识点,这个过程中你会逐步掌握go语言的各种特性。

一、cache2go是什么

  •  原作者说:Concurrency-safe golang caching library with expiration capabilities.

什么意思呢?

  • 有心跳机制的并发安全的go语言缓存库

我想告诉你:学习一门语言有一个很好的方法就是阅读优秀的开源项目源码,学习优秀前辈的编码方式,同时发现自己的知识盲区,不断获取新知识!cache2go很精简,代码量很少,非常适合刚接触go语言的同学作为入门级项目来读源码。

下面我会先介绍项目组成,然后讲解核心数据结构,再梳理整个实现逻辑,来帮助大家掌握整个项目。

准备好了吗?出发!

二、项目结构

CloudGeek读源码系列-cache2go源码解析(一)

项目目录结构如上图所示,可以看到功能实现相关源码文件只有3个:

  1. cache.go
  2. cacheitem.go
  3. cachetable.go

 三、关键数据结构

 项目中只涉及到2个复杂的数据类型,分别是:

  1. CacheItem
  2. CacheTable

 含义和字面意义一致,一个是缓存表,一个是缓存表中的条目。下面分别看一下这2个结构是怎么定义的。

1、CacheItem

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

2、CacheTable

 CacheTable描述了缓存中的一个表项,里面的items属性就是上面讲到的CacheItem类型实例。这里除了常规属性外还有若干函数类型的属性,源码如下所示:

type CacheTable struct {
	sync.RWMutex

	// The table's name.
	name string
	// All cached items.【一个表中的所有条目都存在这个map里,map的key是任意类型,值是CacheItem指针类型】
	items map[interface{}]*CacheItem

	// Timer responsible for triggering cleanup.【负责触发清除操作的计时器】
	cleanupTimer *time.Timer
	// Current timer duration.【清除操作触发的时间间隔】
	cleanupInterval time.Duration

	// The logger used for this table.
	logger *log.Logger

	// Callback method triggered when trying to load a non-existing key.【需要提取一个不存在的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)
}

 如上所示,cache2go的核心数据结构很简洁,应该比较容易理解。当然没有完全理解每个字段这样定义的原因也别急,看完下面的代码逻辑后反过来再看数据结构,肯定就明白每个字段的作用了。

四、代码逻辑

 我们的思路是从下往上分析代码,什么意思呢?就是说先看和item相关的操作,再看包含item的table相关的代码,最后看操作table的cache级别的逻辑。所以下面我们要先看cacheitem.go的代码,接着分析cachetable.go,然后看cache.go,最后看3个文件中的源码互相间关联是什么,最后看example,也就是cache怎么玩~

1、cacheitem.go

CloudGeek读源码系列-cache2go源码解析(一)

如上图,这个源码文件中只包含了一个类型CacheItem和一个函数NewCacheItem()的定义。CacheItem有哪些属性前面已经看过了,下面先看NewCacheItem()函数:

func NewCacheItem(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,
}
}
  •  代码如上所示,NewCacheItem()函数接收3个参数,分别是键、值、存活时间(key、data、lifeSpan),返回一个CacheItem类型实例的指针。
  • 其中createOn和accessedOn设置成了当前时间,aboutToExpire也就是被删除时触发的回调方法暂时设置成nil,不难想到这个函数完成后还需要调用其他方法来设置这个属性。

cacheitem.go中除了CacheItem类型的定义,NewCacheItem()函数的定义外,还有一个部分就是CacheItem的方法定义,一共8个

CloudGeek读源码系列-cache2go源码解析(一)

源码看起来行数不少,内容其实很简单,主要是元素获取操作,这里需要留意读写操作都是加了相应的读写锁的,还记得开头提到的cache2go是一个并发安全的程序吗?并发安全就体现在这些地方。下面最复杂的是最后一个回调函数的设置,这个方法的形参是f func(interface{}),也就是说形参名为f,形参类型是func(interface{}),这是一个函数类型,这个函数类型的参数是一个interface{},也就是空接口,因为任意类型都可以被认为实现了空接口,所以这里可以接收任意类型的实参。也就是说f的类型是一个可以接收任意类型参数的函数类型。有点绕,需要静下心来理解一下哦~

源码如下:

// KeepAlive marks an item to be kept for another expireDuration period.【将accessedOn更新为当前时间】
func (item *CacheItem) KeepAlive() {
	item.Lock()
	defer item.Unlock()
	item.accessedOn = time.Now()
	item.accessCount++
}

// LifeSpan returns this item's expiration duration.
func (item *CacheItem) LifeSpan() time.Duration {
	// immutable
	return item.lifeSpan
}

// AccessedOn returns when this item was last accessed.
func (item *CacheItem) AccessedOn() time.Time {
	item.RLock()
	defer item.RUnlock()
	return item.accessedOn
}

// CreatedOn returns when this item was added to the cache.
func (item *CacheItem) CreatedOn() time.Time {
	// immutable
	return item.createdOn
}

// AccessCount returns how often this item has been accessed.
func (item *CacheItem) AccessCount() int64 {
	item.RLock()
	defer item.RUnlock()
	return item.accessCount
}

// Key returns the key of this cached item.
func (item *CacheItem) Key() interface{} {
	// immutable
	return item.key
}

// Data returns the value of this cached item.
func (item *CacheItem) Data() interface{} {
	// immutable
	return item.data
}

// SetAboutToExpireCallback configures a callback, which will be called right
// before the item is about to be removed from the cache.【设置回调函数,当一个item被移除的时候这个函数会被调用】
func (item *CacheItem) SetAboutToExpireCallback(f func(interface{})) {
	item.Lock()
	defer item.Unlock()
	item.aboutToExpire = f
}

到这里就看完这个源文件了,是不是很轻松呢~

下面继续看另外2个源码文件吧

2、cachetable.go

  • 除了CacheTable绑定方法外的代码

cachetable.go中除了前面介绍过的主要数据结构CacheTable外还有如下2个类型:

CloudGeek读源码系列-cache2go源码解析(一)

下面先看剩下2个类型是怎么定义的:

CloudGeek读源码系列-cache2go源码解析(一)

CacheItemPair非常简单,注释一句话讲的很清楚,是用来映射key到访问计数的

CloudGeek读源码系列-cache2go源码解析(一)

CacheItemPairList明显就是一个CacheItemPair组成的“列表”,在go中对应的就是切片,绑定到CacheItemPairList类型的方法有3个,Swap和Len太直观了,不再赘述,Less方法判断CacheItemPairList中的第i个CacheItemPair和第j个CacheItemPair的AccessCount大小关系,前者大则返回true,反之false。逻辑是这样,用处我们在后面具体看(和自定义排序相关,(二)中我们来看这里的玄机)。

CloudGeek读源码系列-cache2go源码解析(一)

  • CacheTable类型绑定的方法

这个源码文件中除了上面讲到的部分外就只剩下CacheTable类型绑定的方法了,先看一下有多少:

CloudGeek读源码系列-cache2go源码解析(一)

 下面一个个来看吧~


 1.

CloudGeek读源码系列-cache2go源码解析(一)

Count()方法返回的是指定CacheTable中的item条目数,这里的table.items是一个map类型,len函数返回map中元素数量


 2.

CloudGeek读源码系列-cache2go源码解析(一)

Foreach方法接收一个函数参数,方法内遍历了items,把每个key和value都丢给了trans函数来处理,trans函数的参数列表是key interface{}, item *CacheItem,分别对应任意类型的key和CacheItem类型的缓存条目的引用。


3.

CloudGeek读源码系列-cache2go源码解析(一)

 先看一下参数列表:f func(interface{}, ...interface{}) *CacheItem

 形参的意思是:形参名:f,形参类型:func(interface{}, ...interface{}) *CacheItem

形参类型是一个函数类型,这个函数的参数是一个key和不定数目的argument,返回值是CacheItem指针。

然后在SetDataLoader中将这个函数f丢给了table的loadData属性。loadData所指向的方法是什么时候被调用?注释说which will be called when trying to access a non-existing key.也就是访问一个不存在的key时会调用到。也就是说当访问一个不存在的key时,需要调用一个方法,这个方法通过SetDataLoader设定,方法的实现由用户来自定义。


4.

CloudGeek读源码系列-cache2go源码解析(一)

SetAddedItemCallback方法也很直观,当添加一个Item到缓存表中时会被调用的一个方法,绑定到CacheTable.addedItem上,被绑定的这个方法只接收一个参数,但是这里的参数变量名呢?为什么只写了类型*CacheItem?我去作者的github上提一个issue问问吧~开玩笑开玩笑,这里其实不需要形参变量的名字,发现没?

SetAddedItemCallback方法的形参名是f,类型是后面这个函数,也就是说func(*CacheItem)被作为一个类型,这时候假设写成func(item *CacheItem),这里的item用得到吗?看下面这个例子就清晰了:

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

  如上所示,定义变量show为func(int)类型的时候不需要形参变量。这个地方明白的看起来很简单,一时没有想明白的可能心中会纠结一会~

ok,也就是说SetAddedItemCallback方法设置了一个回调函数,当添加一个CacheItem的时候,同时会调用这个回调函数,这个函数可以选择对CacheItem做一些处理,比如打个日志啊什么的。


 5.

CloudGeek读源码系列-cache2go源码解析(一)

看到这里应该很轻松了,和上面的回调函数一样,这个方法是设置删除Item的时候调用的一个方法。


 6.

CloudGeek读源码系列-cache2go源码解析(一)

这个就不需要讲解了,把一个logger实例丢给table的logger属性。日志相关的知识点我会在golang专题中详细介绍。

到这里是不是感觉so easy?没压力?那就继续看第二讲吧~~

《》