golang:缓存库cache2go介绍
是一个简单的缓存库,代码量较少,可以学习锁、goroutine等知识
代码结构
benchmark_test.go
cache.go
cache_test.go
cacheitem.go
cachetable.go
errors.go
cacheitem.go
CacheItem是缓存表中的条目,代码逻辑很简单
/*
* Simple caching library with expiration capabilities
* Copyright (c) 2013-2017, Christian Muehlhaeuser <muesli@gmail.com>
*
* For license see LICENSE.txt
*/
package cache2go
import (
"sync"
"time"
)
type CacheItem struct {
//读写锁,为保证并发性,维持操作的原子性
sync.RWMutex
// 缓存项的key
key interface{}
// 缓存项的值
data interface{}
// 缓存项的生命周期
lifeSpan time.Duration
// 创建的时间戳
createdOn time.Time
// 上次访问的时间
accessedOn time.Time
// 访问的次数
accessCount int64
// 缓存项在删除之前触发的Callback method
aboutToExpire []func(key interface{})
}
//NewCacheItem:
//参数:key 空接口interface{}可以接受任意类型
//参数:lifeSpan 存活时间
//参数:data 缓存的value
//返回值: 返回一个新创建的CacheItem指针
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,
}
}
// KeepAlive 重置生命周期
func (item *CacheItem) KeepAlive() {
item.Lock()
defer item.Unlock()
item.accessedOn = time.Now()
item.accessCount++
}
// LifeSpan 返回缓存项的生命周期
func (item *CacheItem) LifeSpan() time.Duration {
// immutable
return item.lifeSpan
}
// AccessedOn 返回缓存项的最后访问时间
func (item *CacheItem) AccessedOn() time.Time {
item.RLock()
defer item.RUnlock()
return item.accessedOn
}
// CreatedOn 返回缓存项的创建时间
func (item *CacheItem) CreatedOn() time.Time {
// immutable
return item.createdOn
}
// AccessCount 返回缓存项被访问的次数
func (item *CacheItem) AccessCount() int64 {
item.RLock()
defer item.RUnlock()
return item.accessCount
}
// Key 返回当前缓存项的key
func (item *CacheItem) Key() interface{} {
// immutable
return item.key
}
// Data 返回当前缓存项的value
func (item *CacheItem) Data() interface{} {
// immutable
return item.data
}
// SetAboutToExpireCallback 重置回调函数,在缓存项被删除前调用
func (item *CacheItem) SetAboutToExpireCallback(f func(interface{})) {
if len(item.aboutToExpire) > 0 {
item.RemoveAboutToExpireCallback()
}
item.Lock()
defer item.Unlock()
item.aboutToExpire = append(item.aboutToExpire, f)
}
// AddAboutToExpireCallback 添加回调函数
func (item *CacheItem) AddAboutToExpireCallback(f func(interface{})) {
item.Lock()
defer item.Unlock()
item.aboutToExpire = append(item.aboutToExpire, f)
}
// RemoveAboutToExpireCallback 移除回调函数
func (item *CacheItem) RemoveAboutToExpireCallback() {
item.Lock()
defer item.Unlock()
item.aboutToExpire = nil
}
cachetable.go
缓存表
/*
* Simple caching library with expiration capabilities
* Copyright (c) 2013-2017, Christian Muehlhaeuser <muesli@gmail.com>
*
* For license see LICENSE.txt
*/
package cache2go
import (
"log"
"sort"
"sync"
"time"
)
// CacheTable 存储 CacheItem 缓存项
type CacheTable struct {
sync.RWMutex
// 缓存表名
name string
// 所有的缓存项(键值对存储)
items map[interface{}]*CacheItem
// 触发 缓存清理 的定时器
//当Timer到期时,当时的时间会被发送给C
cleanupTimer *time.Timer
// 缓存清理周期
cleanupInterval time.Duration
// 该缓存表的日志
logger *log.Logger
//当试图获取不存在的缓存项时的回调函数
loadData func(key interface{}, args ...interface{}) *CacheItem
//向缓存表中添加缓存项时的回调函数
addedItem []func(item *CacheItem)
// 向缓存表中删除缓存项时的回调函数
aboutToDeleteItem []func(item *CacheItem)
}
// 返回缓存表中当前存储的缓存项数目
func (table *CacheTable) Count() int {
table.RLock()
defer table.RUnlock()
return len(table.items)
}
// 遍历缓存表中的缓存项
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 configures a data-loader callback, which will be called when
// trying to access a non-existing key. The key and 0...n additional arguments
// are passed to the callback function.
func (table *CacheTable) SetDataLoader(f func(interface{}, ...interface{}) *CacheItem) {
table.Lock()
defer table.Unlock()
table.loadData = f
}
// SetAddedItemCallback configures a callback, which will be called every time
// a new item is added to the cache.
func (table *CacheTable) SetAddedItemCallback(f func(*CacheItem)) {
if len(table.addedItem) > 0 {
table.RemoveAddedItemCallbacks()
}
table.Lock()
defer table.Unlock()
table.addedItem = append(table.addedItem, f)
}
//AddAddedItemCallback appends a new callback to the addedItem queue
func (table *CacheTable) AddAddedItemCallback(f func(*CacheItem)) {
table.Lock()
defer table.Unlock()
table.addedItem = append(table.addedItem, f)
}
// RemoveAddedItemCallbacks empties the added item callback queue
func (table *CacheTable) RemoveAddedItemCallbacks() {
table.Lock()
defer table.Unlock()
table.addedItem = nil
}
// SetAboutToDeleteItemCallback configures a callback, which will be called
// every time an item is about to be removed from the cache.
func (table *CacheTable) SetAboutToDeleteItemCallback(f func(*CacheItem)) {
if len(table.aboutToDeleteItem) > 0 {
table.RemoveAboutToDeleteItemCallback()
}
table.Lock()
defer table.Unlock()
table.aboutToDeleteItem = append(table.aboutToDeleteItem, f)
}
// AddAboutToDeleteItemCallback appends a new callback to the AboutToDeleteItem queue
func (table *CacheTable) AddAboutToDeleteItemCallback(f func(*CacheItem)) {
table.Lock()
defer table.Unlock()
table.aboutToDeleteItem = append(table.aboutToDeleteItem, f)
}
// RemoveAboutToDeleteItemCallback empties the about to delete item callback queue
func (table *CacheTable) RemoveAboutToDeleteItemCallback() {
table.Lock()
defer table.Unlock()
table.aboutToDeleteItem = nil
}
// 配置当前缓存表要用的日志
func (table *CacheTable) SetLogger(logger *log.Logger) {
table.Lock()
defer table.Unlock()
table.logger = logger
}
// self-adjusting timer.计时器触发 循环 过期 检查
func (table *CacheTable) expirationCheck() {
table.Lock()
if table.cleanupTimer != nil {
table.cleanupTimer.Stop()//Stop停止Timer的执行。
}
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)
}
// To be more accurate with timers, we would need to update 'now' on every
// loop iteration. Not sure it's really efficient though.
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 {//永久有效
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()
})
}
//AfterFunc另起一个go程等待时间段d过去,然后调用f。它返回一个*Timer,可以通过调用其Stop方法来取消等待和对f的调用。
table.Unlock()
}
func (table *CacheTable) addInternal(item *CacheItem) {
// Careful: 除非表被锁,不要调用此方法
// 它 将在运行回调和检查时将此表解锁
table.log("Adding item with key", item.key, "and lifespan of", item.lifeSpan, "to table", table.name)
table.items[item.key] = item
// Cache values so we don't keep blocking the mutex.
expDur := table.cleanupInterval
addedItem := table.addedItem
table.Unlock()
// 在增加缓存项后,触发回调函数
if addedItem != nil {
for _, callback := range addedItem {
callback(item)
}
}
// 如果没有设置table检查时间,或者找到了检查时间更早的缓存项
if item.lifeSpan > 0 && (expDur == 0 || item.lifeSpan < expDur) {
table.expirationCheck()
}
}
//添加缓存项到缓存表中
func (table *CacheTable) Add(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {
item := NewCacheItem(key, lifeSpan, data)
// Add item to cache.
table.Lock()
table.addInternal(item)
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()
// Trigger callbacks before deleting an item from cache.
if aboutToDeleteItem != nil {
for _, callback := range aboutToDeleteItem {
callback(r)
}
}
r.RLock()
defer r.RUnlock()
if r.aboutToExpire != nil {
for _, callback := range r.aboutToExpire {
callback(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
}
// Delete an item from the cache.
func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) {
table.Lock()
defer table.Unlock()
return table.deleteInternal(key)
}
//缓存项是否存在
func (table *CacheTable) Exists(key interface{}) bool {
table.RLock()
defer table.RUnlock()
_, ok := table.items[key]
return ok
}
// NotFoundAdd 检查缓存项是否存在,不存在就添加一条缓存项
func (table *CacheTable) NotFoundAdd(key interface{}, lifeSpan time.Duration, data interface{}) bool {
table.Lock()
if _, ok := table.items[key]; ok {
table.Unlock()
return false
}
item := NewCacheItem(key, lifeSpan, data)
table.addInternal(item)
return true
}
// Value returns an item from the cache and marks it to be kept alive.
//可以向 DataLoader callback function 传递 额外的参数
func (table *CacheTable) Value(key interface{}, args ...interface{}) (*CacheItem, error) {
table.RLock()
r, ok := table.items[key]
loadData := table.loadData
table.RUnlock()
if ok {
// 更新时间戳,访问次数
r.KeepAlive()
return r, nil
}
// Item doesn't exist in cache. Try and fetch it with a data-loader.
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
}
// 重置整个缓存表
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 是 CacheItemPair 切片 ,并实现了排序
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 返回访问次数最多的count个缓存项(切片)
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...)
}
type Timer
type Timer struct {
C <-chan Time
// 内含隐藏或非导出字段
}
Timer类型代表单次时间事件。当Timer到期时,当时的时间会被发送给C
Sub
func (t Time) Sub(u Time) Duration
返回一个时间段t-u
func AfterFunc
func AfterFunc(d Duration, f func()) *Timer
AfterFunc另起一个go程等待时间段d过去,然后调用f。它返回一个Timer,可以通过调用其Stop方法来取消等待和对f的调用。
MostAccessed
实现[]CacheItemPair由大到小排序
sort.Sort
type Interface interface {
// Len方法返回集合中的元素个数
Len() int
// Less方法报告索引i的元素是否比索引j的元素小
Less(i, j int) bool
// Swap方法交换索引i和j的两个元素
Swap(i, j int)
}
一个满足sort.Interface接口的(集合)类型可以被本包的函数进行排序。方法要求集合中的元素可以被整数索引。
要做啥?
Foreach
// 遍历缓存表中的缓存项
func (table *CacheTable) Foreach(trans func(key interface{}, item *CacheItem)) {
table.RLock()
defer table.RUnlock()
for k, v := range table.items {
trans(k, v)
}
}
cache.go
/*
* Simple caching library with expiration capabilities
* Copyright (c) 2012, Radu Ioan Fericean
* 2013-2017, Christian Muehlhaeuser <muesli@gmail.com>
*
* For license see LICENSE.txt
*/
package cache2go
import (
"sync"
)
var (
cache = make(map[string]*CacheTable)
mutex sync.RWMutex
)
// 返回一个已有的缓存表或者新建一个缓存表
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
}
errors.go
/*
* Simple caching library with expiration capabilities
* Copyright (c) 2013-2017, Christian Muehlhaeuser <muesli@gmail.com>
*
* For license see LICENSE.txt
*/
package cache2go
import (
"errors"
)
var (
// ErrKeyNotFound gets returned when a specific key couldn't be found
ErrKeyNotFound = errors.New("Key not found in cache")
// ErrKeyNotFoundOrLoadable gets returned when a specific key couldn't be
// found and loading via the data-loader callback also failed
ErrKeyNotFoundOrLoadable = errors.New("Key not found and could not be loaded into cache")
)
使用示例
mycachedapp.go
package main
import (
"fmt"
"time"
"github.com/muesli/cache2go"
)
// Keys & values in cache2go can be of arbitrary types, e.g. a struct.
type myStruct struct {
text string
moreData []byte
}
func main() {
// 获取一个新的缓存表
cache := cache2go.Cache("myCache")
//新建一个缓存项
val := myStruct{"This is a test!", []byte{}}
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)
}
// 等待过期后再次尝试获取缓存项
time.Sleep(6 * time.Second)
res, err = cache.Value("someKey")
if err != nil {
fmt.Println("Item is not cached (anymore).")
}
// 添加一个不过期的缓存项
cache.Add("someKey", 0, &val)
// cache2go supports a few handy callbacks and loading mechanisms.
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")
// And wipe the entire cache table.
cache.Flush()
}
Found value in cache: This is a test!
Item is not cached (anymore).
Deleting: someKey This is a test! 2022-03-06 11:10:46.512861 +0800 CST m=+6.006666301
callbacks.go
package main
import (
"fmt"
"time"
"github.com/muesli/cache2go"
)
func main() {
cache := cache2go.Cache("myCache")
// This callback will be triggered every time a new item
// gets added to the cache.
cache.SetAddedItemCallback(func(entry *cache2go.CacheItem) {
fmt.Println("Added Callback 1:", entry.Key(), entry.Data(), entry.CreatedOn())
})
cache.AddAddedItemCallback(func(entry *cache2go.CacheItem) {
fmt.Println("Added Callback 2:", entry.Key(), entry.Data(), entry.CreatedOn())
})
// This callback will be triggered every time an item
// is about to be removed from the cache.
cache.SetAboutToDeleteItemCallback(func(entry *cache2go.CacheItem) {
fmt.Println("Deleting:", entry.Key(), entry.Data(), entry.CreatedOn())
})
// Caching a new item will execute the AddedItem callback.
cache.Add("someKey", 0, "This is a test!")
// Let's retrieve the item from the cache
res, err := cache.Value("someKey")
if err == nil {
fmt.Println("Found value in cache:", res.Data())
} else {
fmt.Println("Error retrieving value from cache:", err)
}
// Deleting the item will execute the AboutToDeleteItem callback.
cache.Delete("someKey")
cache.RemoveAddedItemCallbacks()
// 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))
})
time.Sleep(5 * time.Second)
}
Added Callback 1: someKey This is a test! 2022-03-06 11:14:31.8244035 +0800 CST m=+0.003562701
Added Callback 2: someKey This is a test! 2022-03-06 11:14:31.8244035 +0800 CST m=+0.003562701
Found value in cache: This is a test!
Deleting: someKey This is a test! 2022-03-06 11:14:31.8244035 +0800 CST m=+0.003562701
Deleting: anotherKey This is another test 2022-03-06 11:14:31.8407515 +0800 CST m=+0.019910701
About to expire: anotherKey
dataloader.go
package main
import (
"fmt"
"github.com/muesli/cache2go"
"strconv"
)
func main() {
cache := cache2go.Cache("myCache")
// 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 < 10; 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)
}
}
}
Found value in cache: This is a test with key someKey_0
Found value in cache: This is a test with key someKey_1
Found value in cache: This is a test with key someKey_2
Found value in cache: This is a test with key someKey_3
Found value in cache: This is a test with key someKey_4
Found value in cache: This is a test with key someKey_5
Found value in cache: This is a test with key someKey_6
Found value in cache: This is a test with key someKey_7
Found value in cache: This is a test with key someKey_8
Found value in cache: This is a test with key someKey_9