golang:缓存库cache2go介绍
文章目录
- 是什么
- 项目结构
- 设计原理
- 关键数据结构
- CacheItem
- CacheTable
- cache.go
- 例子
是什么
- 带有时效性的单机缓存
项目结构
项目地址: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
还没有评论,来说两句吧...