Go缓存库cache2go介绍

是什么

在这里插入图片描述

  • 带有时效性的单机缓存

项目结构

项目地址:https://github.com/muesli/cache2go
在这里插入图片描述

设计原理

在这里插入图片描述

关键数据结构

  • CacheItem:缓存表中的条目
  • CacheTable :缓存表

CacheItem

没什么好看的,除了需要注意一下cacheItem的结构之外


import (
	"sync"
	"time"
)


type CacheItem struct {
	sync.RWMutex  //读写锁:为了保证其并发安全性,都带有sync.RWMutex,维持操作的原子性。并带有时间戳来实现过期控制。

	key interface{}   //缓存项的key
	data interface{}   //缓存项的value
	lifeSpan time.Duration//缓存的生命周期:当不再被访问时,该项目在缓存中生存多久

	createdOn time.Time  //缓存项目的创建时间戳。
	accessedOn time.Time  //缓存项目上一次被访问的时间戳
	accessCount int64   //缓存项目被访问的次数

	aboutToExpire func(key interface{})  // // 在删除缓存项之前调用的回调函数
}

// NewCacheItem返回一个新创建的CacheItem的指针
// 参数key时缓存项目的缓存密码key
// 参数lifeSpan决定经过多久之后将该缓存项删除【存活时间】
// 参数data是缓存项的value
func NewCacheItem(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {
	t := time.Now()
	return &CacheItem{
		key:           key,
		lifeSpan:      lifeSpan,
		createdOn:     t,
		accessedOn:    t, //createdOn和accessedOn设置成了当前时间
		accessCount:   0,
		aboutToExpire: nil,  //被删除时触发的回调方法初始化为nil【推测还应当调用其他方法来设置这个属性】
		data:          data, 
	}
}

// KeepAlive标记一个项目保持另一个expireDuration(持续时间)周期
func (item *CacheItem) KeepAlive() {
	item.Lock()
	defer item.Unlock()
	item.accessedOn = time.Now() //记录此时访问的时间
	item.accessCount++  //增加被访问的次数
}

// LifeSpan返回CacheItem的生命期限。
func (item *CacheItem) LifeSpan() time.Duration {
	// immutable
	return item.lifeSpan
}

// AccessedOn返回CacheItem最后被访问的时间
func (item *CacheItem) AccessedOn() time.Time {
	item.RLock()
	defer item.RUnlock()
	return item.accessedOn
}

// CreatedOn返回当前CacheItem的最开始创建的时间
func (item *CacheItem) CreatedOn() time.Time {
	// immutable
	return item.createdOn
}

// AccessCount返回当前CacheItem被访问的次数
func (item *CacheItem) AccessCount() int64 {
	item.RLock()
	defer item.RUnlock()
	return item.accessCount
}

// Key返回当前CacheItem中的key
func (item *CacheItem) Key() interface{} {
	// immutable
	return item.key
}

// Data返回当前CacheItem的value
func (item *CacheItem) Data() interface{} {
	// immutable
	return item.data
}

// SetAboutToExpireCallback配置被删除之前要调用的回调函数 
func (item *CacheItem) SetAboutToExpireCallback(f func(interface{})) {
	item.Lock()
	defer item.Unlock()
	item.aboutToExpire = f
}

在这里插入图片描述

CacheTable

package cache2go

import (
	"log"
	"sort"
	"sync"
	"time"
)

// CacheTable is a table within the cache
type CacheTable struct {
	sync.RWMutex

	// 缓存表名
	name string
	//所有的缓存项
	items map[interface{}]*CacheItem

	//负责触发缓存清理的定时器。
	cleanupTimer *time.Timer
	// 缓存清理周期
	cleanupInterval time.Duration

	// 该缓存表的日志
	logger *log.Logger

	//当试图获取一个不存在的缓存项时的回调函数
	loadData func(key interface{}, args ...interface{}) *CacheItem
	//当向缓存表中增加一个缓存项时的回调函数
	addedItem func(item *CacheItem)
	//当从缓存表中删除一个缓存项时被调用的函数
	aboutToDeleteItem func(item *CacheItem)
}

// Count returns返回当前缓存中存储有多少个缓存项:求map的长度
func (table *CacheTable) Count() int {
	table.RLock()
	defer table.RUnlock()
	return len(table.items)
}

//遍历所有的缓存项:遍历map
func (table *CacheTable) Foreach(trans func(key interface{}, item *CacheItem)) {
	table.RLock()
	defer table.RUnlock()

	for k, v := range table.items {
		trans(k, v)
	}
}

// SetDataLoader配置【当试图获取一个不存在的缓存项时】的回调函数
func (table *CacheTable) SetDataLoader(f func(interface{}, ...interface{}) *CacheItem) {
	table.Lock()
	defer table.Unlock()
	table.loadData = f
}

// SetAddedItemCallback配置【当向缓存表中增加一个缓存项时的回调函数
func (table *CacheTable) SetAddedItemCallback(f func(*CacheItem)) {
	table.Lock()
	defer table.Unlock()
	table.addedItem = f
}

// SetAboutToDeleteItemCallback配置【当从缓存表中删除一个缓存项时】被调用的函数
func (table *CacheTable) SetAboutToDeleteItemCallback(f func(*CacheItem)) {
	table.Lock()
	defer table.Unlock()
	table.aboutToDeleteItem = f
}

//SetLogger配置当前缓存表所使用的日志
func (table *CacheTable) SetLogger(logger *log.Logger) {
	table.Lock()
	defer table.Unlock()
	table.logger = logger
}

//由计时器触发的到期检查.
func (table *CacheTable) expirationCheck() {
	table.Lock()
	if table.cleanupTimer != nil {  //不设置为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)   //不设置为0
	}

	// 为了更准确的使用定时器,我们应该再每个记时时间周期跟新now()。不确定是否真的有效
	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()

		if lifeSpan == 0 {  //0永久有效
			continue
		}
		if now.Sub(accessedOn) >= lifeSpan {
			// 项目超过了项目周期则删除项目
			table.deleteInternal(key)
		} else {
			// 查询最靠近死亡周期的项目
			if smallestDuration == 0 || lifeSpan-now.Sub(accessedOn) < smallestDuration {
				smallestDuration = lifeSpan - now.Sub(accessedOn)
			}
		}
	}

	//为下次清理设置间隔
	table.cleanupInterval = smallestDuration
	if smallestDuration > 0 {
		table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
			go table.expirationCheck()
		})
	}
	table.Unlock()
}
//
func (table *CacheTable) addInternal(item *CacheItem) {
	// Careful: 除非表互斥锁被锁定,不要调用此方法
	// 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  //将item加入缓存表中

	// Cache values so we don't keep blocking the mutex.
	expDur := table.cleanupInterval  //默认为0
	addedItem := table.addedItem  //设置item加入缓存表中的回调函数,[如果没有使用SetAddedItemCallback设置的化就是nil]
	table.Unlock()

	// 将item加入cache表中是触发
	if addedItem != nil { //如果设置了回调函数
		addedItem(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()
	}
}

// Add创建一个CacheItem,加锁,调用addInternal:传参:key,生命周期,和key-value中的value
func (table *CacheTable) Add(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {
	item := NewCacheItem(key, lifeSpan, data)

	//增加一个item到cache
	table.Lock()
	table.addInternal(item) //传参就是新创建的CacheItem

	return item
}

func (table *CacheTable) deleteInternal(key interface{}) (*CacheItem, error) {
	r, ok := table.items[key]
	if !ok {
		return nil, ErrKeyNotFound
	}

	// Cache value so we don't keep blocking the mutex.
	aboutToDeleteItem := table.aboutToDeleteItem
	table.Unlock()

	//从缓存中删除item之前触发回调函数
	if aboutToDeleteItem != nil {
		aboutToDeleteItem(r)
	}

	r.RLock()
	defer r.RUnlock()
	if r.aboutToExpire != nil {
		r.aboutToExpire(key)
	}

	table.Lock()
	table.log("Deleting item with key", key, "created on", r.createdOn, "and hit", r.accessCount, "times from table", table.name)
	delete(table.items, key)

	return r, nil
}

// 从缓存中删除缓存
func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) {
	table.Lock()
	defer table.Unlock()

	return table.deleteInternal(key)
}

// Exists判断item是否存在cache中.
func (table *CacheTable) Exists(key interface{}) bool {
	table.RLock()
	defer table.RUnlock()
	_, ok := table.items[key]

	return ok
}

// NotFoundAdd测试item是否在缓存表中. 如果没有找到就新创建一个item并加入缓存表中
func (table *CacheTable) NotFoundAdd(key interface{}, lifeSpan time.Duration, data interface{}) bool {
	table.Lock()

	if _, ok := table.items[key]; ok {  //遍历map表,如果找到就返回false
		table.Unlock()
		return false
	}

	item := NewCacheItem(key, lifeSpan, data)  //如果当前没有对应key-value,就新创建一个item并加入缓存表,然后返回true
	table.addInternal(item)

	return true
}

// Value returns an item from the cache and marks it to be kept alive. You can
// pass additional arguments to your DataLoader callback function.
func (table *CacheTable) Value(key interface{}, args ...interface{}) (*CacheItem, error) {
	table.RLock()
	r, ok := table.items[key] //从table表也就是map中查找key
	loadData := table.loadData //如果没有专门就是就是nil
	table.RUnlock()

	if ok {
		//更新访问时间戳和访问次数加1
		r.KeepAlive()  
		return r, nil
	}

	// 如果查找item不存在缓存表中. 可以设置data-loader回调然后加入到cache表中.
	//即如果我们去查找某个key的缓存,如果找不到且我们设置了dataloader回调,就会执行该回调函数。这个功能还是挺实用的,举个例子比如我们缓存了数据库中的一些用户信息,如果我们可以设置dataloader回调,
	//如果从缓存里面查找某个用户信息时没有找到,就从数据库中读取该用户信息并加到缓存里面,这个动作就可以加在dataloader回调里面
	if loadData != nil {
		item := loadData(key, args...)
		if item != nil {
			table.Add(key, item.lifeSpan, item.data)
			return item, nil
		}

		return nil, ErrKeyNotFoundOrLoadable
	}

	return nil, ErrKeyNotFound
}

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

// CacheItemPair maps key to access counter
type CacheItemPair struct {
	Key         interface{}
	AccessCount int64
}

// CacheItemPairList is a slice of CacheIemPairs that implements sort.
// Interface to sort by AccessCount.
type CacheItemPairList []CacheItemPair

func (p CacheItemPairList) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p CacheItemPairList) Len() int           { return len(p) }
func (p CacheItemPairList) Less(i, j int) bool { return p[i].AccessCount > p[j].AccessCount }

// MostAccessed returns返回当前缓存表中被访问最多的表
func (table *CacheTable) MostAccessed(count int64) []*CacheItem {
	table.RLock()
	defer table.RUnlock()

	p := make(CacheItemPairList, len(table.items))
	i := 0
	for k, v := range table.items {
		p[i] = CacheItemPair{k, v.accessCount}
		i++
	}
	sort.Sort(p)

	var r []*CacheItem
	c := int64(0)
	for _, v := range p {
		if c >= count {
			break
		}

		item, ok := table.items[v.Key]
		if ok {
			r = append(r, item)
		}
		c++
	}

	return r
}

// Internal logging method for convenience.
func (table *CacheTable) log(v ...interface{}) {
	if table.logger == nil {
		return
	}

	table.logger.Println(v...)
}


cache.go

//总结:根据表名返回指向对应的表的地址。如果不存在该表就创建一个表
package cache2go

import (
	"sync"
)

var (
	cache = make(map[string]*CacheTable)  //cacheTable实质是一个map,key是string,value是*CacheTable;
	mutex sync.RWMutex
)

// Cache[创建一个新表]返回已经存在的缓存表
func Cache(table string) *CacheTable {
	mutex.RLock()
	t, ok := cache[table]  //如果没有找到table名对应的cache表,
	mutex.RUnlock()

	if !ok {
		mutex.Lock()
		t, ok = cache[table]
		// 第二次检查表是否存在
		if !ok {
			t = &CacheTable{  //如果不存在,就定义一个map表[CacheTable]并取出表的地址
				name:  table,  //表名
				items: make(map[interface{}]*CacheItem), //初始化一个CacheItem结构的map
			}
			cache[table] = t  //将表的地址返回
		}
		mutex.Unlock()
	}

	return t
}

例子

package main

import (
	"fmt"
	"github.com/muesli/cache2go"
	"strconv"
	"time"
)
type myStruct struct {
	text     string
	moreData []byte
}

func main() {
	cache := cache2go.Cache("myCache")


	val := myStruct{"This is a test!", []byte{}}

	cache.SetAddedItemCallback(func(entry *cache2go.CacheItem){
		fmt.Println("Added:", entry.Key(), entry.Data(), "---", entry.CreatedOn())
	})

	cache.Add("someKey", 5*time.Second, &val)

	res, err := cache.Value("someKey")
	if err == nil {
		fmt.Println("Found value in cache:", res.Data().(*myStruct).text)
	} else {
		fmt.Println("Error retrieving value from cache:", err)
	}

	// Wait for the item to expire in cache.
	//time.Sleep(6 * time.Second)
	//res, err = cache.Value("someKey")
	//if err != nil {
	//	fmt.Println("Item is not cached (anymore).")
	//}

	cache.SetAboutToDeleteItemCallback(func(e *cache2go.CacheItem) {
		fmt.Println("Deleting:", e.Key(), e.Data().(*myStruct).text, e.CreatedOn())
	})

	// Remove the item from the cache.
	cache.Delete("someKey")

	cache.Flush()

	//--------------------------------------------------------
	/*之前介绍的回调函数都是在添加或删除缓存表项时候触发,而这个dataloader回调则是在调用Value时触发。即如果我们去查找某个key的缓存,如果找不到且我们设置了dataloader回调,就会执行该回调函数。这个功能还是挺实用的,举个例子比如我们缓存了数据库中的一些用户信息,如果我们可以设置dataloader回调,
	如果从缓存里面查找某个用户信息时没有找到,就从数据库中读取该用户信息并加到缓存里面,这个动作就可以加在dataloader回调里面。*/
	// The data loader gets called automatically whenever something
	// tries to retrieve a non-existing key from the cache.
	cache.SetDataLoader(func(key interface{}, args ...interface{}) *cache2go.CacheItem {
		// Apply some clever loading logic here, e.g. read values for
		// this key from database, network or file.
		val := "This is a test with key " + key.(string)

		// This helper method creates the cached item for us. Yay!
		item := cache2go.NewCacheItem(key, 0, val)
		return item
	})

	// Let's retrieve a few auto-generated items from the cache.
	for i := 0; i < 1; i++ {
		res, err := cache.Value("someKey_" + strconv.Itoa(i))
		if err == nil {
			fmt.Println("Found value in cache:", res.Data())
		} else {
			fmt.Println("Error retrieving value from cache:", err)
		}
	}

	fmt.Println(cache.Count())
}

参考:https://time-track.cn/cache2go-introduction.html
https://blog.csdn.net/Paddy90/article/details/72884578

猜你喜欢

转载自blog.csdn.net/zhizhengguan/article/details/84257338