cache2go源码分析

前言

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缓存

  1. 会进行一次缓存检查,如果到期还进行清除掉
  2. 还没有到期的,遍历会计算出一个最短时间,并生成一个定时任务,定时清除
  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值