前言
cache2go是一个优秀的开源项目,具有过期功能的并发安全的golang缓存库。
源码分析
定义结构
定义一个缓存表
type CacheTable struct {
sync.RWMutex
// 一个表名
name string
// 全部缓存项
items map[interface{}]*CacheItem
// 清理时间触发
cleanupTimer *time.Timer
// 清理时间间隔
cleanupInterval time.Duration
logger *log.Logger
// 尝试不存在key的时候回调的方法
loadData func(key interface{}, args ...interface{}) *CacheItem
// 加入缓存时回调方法
addedItem []func(item *CacheItem)
// 删除缓存时回调方
aboutToDeleteItem []func(item *CacheItem)
}
定义一个缓存数据结构
type CacheItem struct {
sync.RWMutex
// 一个缓存的key
key interface{}
// 一个缓存的数据
data interface{}
// 存在多久没有被访问
lifeSpan time.Duration
// 创建时间
createdOn time.Time
// 最后被访问的时间
accessedOn time.Time
// 访问次数
accessCount int64
// 在从缓存中删除项目之前触发的回调方法。
aboutToExpire []func(key interface{})
}
创建缓存表
var (
cache = make(map[string]*CacheTable)
mutex sync.RWMutex
)
func Cache(table string) *CacheTable {
mutex.RLock()
t, ok := cache[table]
mutex.RUnlock()
// 如果map的key不存在,就要创建
if !ok {
mutex.Lock()
t, ok = cache[table]
// 双重检查表存不存在
if !ok {
t = &CacheTable{
name: table,
items: make(map[interface{}]*CacheItem),
}
}
}
return t
}
分析cacheitem.go代码
// 获取新缓存方法,返回缓存指针
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,
}
}
// 继续存活
func (item *CacheItem) KeepAlive() {
// 锁着当前缓存
item.Lock()
// 执行完此方法后解锁
defer item.Unlock()
// 更新最新时间
item.accessedOn = time.Now()
// 被访问一次
item.accessCount++
}
// 返回这缓存的有效时间
func (item *CacheItem) LifeSpan() time.Duration {
// 不可变
return item.lifeSpan
}
// 并发安全获取缓存已存活时间
func (item *CacheItem) AccessedOn() time.Time {
item.Lock()
defer item.Unlock()
return item.accessedOn
}
// 并发安全获取缓存已访问次数
func (item *CacheItem) AccessedCount() int64 {
item.Lock()
defer item.Unlock()
return item.AccessedCount()
}
// 返回缓存key
func (item *CacheItem) Key() interface{} {
// 不可变量
return item.key
}
// 返回缓存数据
func (item *CacheItem) Data() interface{} {
// 不可变量
return item.data
}
分析cachetable.go代码
package cache2gorw
import (
"fmt"
"log"
"sync"
"time"
)
type CacheTable struct {
sync.RWMutex
// 一个表名
name string
// 全部缓存项
items map[interface{}]*CacheItem
// 清理时间触发
cleanupTimer *time.Timer
// 清理时间间隔
cleanupInterval time.Duration
logger *log.Logger
// 尝试不存在key的时候回调的方法
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)
}
}
func (table *CacheTable) SetDataLoader(f func(interface{}, ...interface{}) *CacheItem) {
table.Lock()
defer table.Unlock()
table.loadData = f
}
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)
}
func (table *CacheTable) RemoveAddedItemCallbacks() {
table.Lock()
defer table.Unlock()
table.addedItem = nil
}
// 加入缓存
func (table *CacheTable) Add(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {
// 创建一个缓存对象
item := NewCacheItem(key, lifeSpan, data)
// 将缓存对象加入缓存表
table.Lock()
// 加入缓存内部方法
table.addInternal(item)
return item
}
func (table *CacheTable) addInternal(item *CacheItem) {
// 注意:执行这个方法之前,table一定要上锁
// 执行这个方法会先解锁
table.log("Adding item with key", item.key, "and lifespan of", item.lifeSpan, "to table", table.name)
// 对象被放入map集合中
table.items[item.key] = item
expDur := table.cleanupInterval
addedItem := table.addedItem
table.Unlock()
// 执行回调方法
if addedItem != nil {
for _, callback := range addedItem {
callback(item)
}
}
// 有过期的缓存 和 清除时间间隔=0或存活时间<清除时间间隔
if item.lifeSpan > 0 && (expDur == 0 || item.lifeSpan < expDur) {
table.expirationCheck()
}
}
// 检查过期缓存
func (table *CacheTable) expirationCheck() {
table.Lock()
if table.cleanupTimer != 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)
}
now := time.Now()
smallestDuration := 0 * time.Second
for key, item := range table.items {
item.RLock()
lifeSpan := item.lifeSpan
accessedOn := item.accessedOn
item.RUnlock()
// 缓存lifeSpan=0是无有效期,直接跳过
if lifeSpan == 0 {
continue
}
// 现在时间-最后访问时间 >= 存活时间 >>> 说明已过期
if now.Sub(accessedOn) >= lifeSpan {
fmt.Printf("[%s] check and delete expire key:%s\n", time.Now(), key)
// 缓存需要删除
table.deleteInternal(item)
} 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) deleteInternal(key interface{}) (*CacheItem, error) {
// 检查是否有这个缓存
r, ok := table.items[key]
if !ok {
return nil, ErrKeyNotFound
}
aboutToDeleteItem := table.aboutToDeleteItem
table.Unlock()
if aboutToDeleteItem != nil {
for _, callback := range aboutToDeleteItem {
callback(r)
}
}
r.RLock()
defer r.RUnlock()
if r.aboutToExpire != nil {
for _, callback := range aboutToDeleteItem {
callback(r)
}
}
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)
}
// 判断是否存在此缓存
func (table *CacheTable) Exists(key interface{}) bool {
table.RLock()
defer table.RUnlock()
_, ok := table.items[key]
return ok
}
// 获取缓存
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
}
// 如果在缓存map无法找到缓存,则在数据上传器里面获取
if loadData != nil {
item := loadData(key, args...)
if item != nil {
table.Add(key, item.lifeSpan, item.data)
return item, nil
}
}
// 如果map和loadData都为空,则无此缓存
return nil, ErrKeyNotFound
}
// 内部log方法
func (table *CacheTable) log(v ...interface{}) {
if table.logger == nil {
return
}
table.logger.Println(v...)
}
如何并发安全
// 加入缓存
func (table *CacheTable) Add(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {
// 创建一个缓存对象
item := NewCacheItem(key, lifeSpan, data)
// 将缓存对象加入缓存表
table.Lock()
// 加入缓存内部方法
table.addInternal(item)
return item
}
func (table *CacheTable) addInternal(item *CacheItem) {
// 注意:执行这个方法之前,table一定要上锁
// 执行这个方法会先解锁
table.log("Adding item with key", item.key, "and lifespan of", item.lifeSpan, "to table", table.name)
// 对象被放入map集合中
table.items[item.key] = item
expDur := table.cleanupInterval
addedItem := table.addedItem
table.Unlock()
// 执行回调方法
if addedItem != nil {
for _, callback := range addedItem {
callback(item)
}
}
// 有过期的缓存 和 清除时间间隔=0或存活时间<清除时间间隔
if item.lifeSpan > 0 && (expDur == 0 || item.lifeSpan < expDur) {
table.expirationCheck()
}
}
可以从添加缓存的代码中分析,在进行Add的时候,会进行一次加锁table.Lock(),在将缓存放入集合后,再table.Unlock(),而这个是对一个缓存表进行加锁,不同的缓存表是可以同时操作的。
同一个缓存表是会并发安全。
如何具有过期功能
在Add缓存
- 会进行一次缓存检查,如果到期还进行清除掉
- 还没有到期的,遍历会计算出一个最短时间,并生成一个定时任务,定时清除