cache2go源码分析

前言

cache2go是一个优秀的开源项目,具有过期功能的并发安全的golang缓存库。

源码分析

定义结构

定义一个缓存表

type CacheTable struct {sync.RWMutex// 一个表名name string// 全部缓存项items map[interface{}]*CacheItem// 清理时间触发cleanupTimer *time.Timer// 清理时间间隔cleanupInterval time.Durationlogger *log.Logger// 尝试不存在key的时候回调的方法loadData func(key interface{}, args ...interface{}) *CacheItem// 加入缓存时回调方法addedItem []func(item *CacheItem)// 删除缓存时回调方aboutToDeleteItem []func(item *CacheItem)
}

定义一个缓存数据结构

type CacheItem struct {sync.RWMutex// 一个缓存的keykey 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 cache2gorwimport ("fmt""log""sync""time"
)type CacheTable struct {sync.RWMutex// 一个表名name string// 全部缓存项items map[interface{}]*CacheItem// 清理时间触发cleanupTimer *time.Timer// 清理时间间隔cleanupInterval time.Durationlogger *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] = itemexpDur := table.cleanupIntervaladdedItem := table.addedItemtable.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.Secondfor key, item := range table.items {item.RLock()lifeSpan := item.lifeSpanaccessedOn := item.accessedOnitem.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 = smallestDurationif 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.aboutToDeleteItemtable.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.loadDatatable.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] = itemexpDur := table.cleanupIntervaladdedItem := table.addedItemtable.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. 还没有到期的,遍历会计算出一个最短时间,并生成一个定时任务,定时清除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/32918.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于JSP的“塞纳河畔左岸”的咖啡馆管理系统

开头语&#xff1a; 塞纳河畔左岸的咖啡&#xff0c;我手一杯品尝的你美~ 哎哟&#xff0c;不错哦&#xff01;我们今天来介绍一下咖啡馆管理系统&#xff01; 你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果你对咖啡馆管理系统感兴趣或有相关需求&#xff0c;欢迎联…

AGV机器人的调度开发分析(1)- 内核中的路线规划

准备开始写一个系列&#xff0c;介绍下AGV机器人的调度的开发和应用。 按照openTCS的核心内容&#xff0c;国内多家广泛应用于AGV的调度。那么架构图如下&#xff1a; Kernel中有一个是Routing&#xff0c;这是路由规划模块&#xff0c;需要实现的细节功能包括如下&#xff1a…

PDF 文件的加密与去密

一、参考内容 GitHub&#xff1a;PDF 文件的加密与去密

两个函数实现C语言日志记录(Linux/Ubuntu 24.04环境)

日志记录的结果如下所示&#xff1a; ------------ 2024_06_23_15_12_48.txt ------------ [2024-06-23 15:12:48][0.258337] web server root path: /home/buda/code/buda/vue/dist [2024-06-23 15:12:48][0.91629] web server listen port: 8888 [2024-06-23 15:12:48][0.5…

Django 模版转义

1&#xff0c;模版转义的作用 Django模版系统默认会自动转义所有变量。这意味着&#xff0c;如果你在模版中输出一个变量&#xff0c;它的内容会被转义&#xff0c;以防止跨站脚本攻击&#xff08;XSS&#xff09;。例如&#xff0c;如果你的变量包含HTML标签&#xff0c;这些…

长亭谛听教程部署和详细教程

PPT 图片先挂着 挺概念的 谛听的能力 hw的时候可能会问你用过的安全产品能力能加分挺重要 溯源反制 反制很重要感觉很厉害 取证分析 诱捕牵制 其实就是蜜罐 有模板直接爬取某些网页模板进行伪装 部署要求 挺低的 对linux内核版本有要求 需要root 还有系统配置也要修改 …

网络编程篇:HTTP协议

一.预备知识 在客户端访问服务端时&#xff0c;要用ipport&#xff0c;但是在日常用户访问服务端的时候&#xff0c;并不会直接使用ip&#xff0c;而是使用域名&#xff0c;比如&#xff1a;百度(www.baidu,com)。 …

智能优化算法改进策略之局部搜索算子(八)--Powell方法

1、原理介绍 Powell方法[1]是一种无约束优化算法&#xff0c;又称为方向加速法&#xff0c;用于寻找多变量函数的极小值。其基本思想是在迭代中逐次产生Q共轭方向组&#xff0c;本质上它属于不需计算导数的共轭方向法。每次迭代后&#xff0c;算法会更新搜索方向&#xff0c;并…

Java内存泄漏检测和分析介绍

在Java中&#xff0c;内存泄漏检测和分析是一个重要的任务&#xff0c;可以通过以下几种方式进行&#xff1a; 1. 使用VisualVM VisualVM是一个可视化工具&#xff0c;可以监控、分析Java应用程序的内存消耗。它可以显示堆内存、垃圾收集、线程等信息&#xff0c;并且可以对内…

英伟达下一代DLSS或利用人工智能

英伟达的黄仁勋在2024年Computex展会上的问答环节中&#xff0c;提前透露了公司未来几代深度学习超采样&#xff08;DLSS&#xff09;技术的发展方向。在回答有关DLSS的问题时&#xff0c;黄仁勋表示&#xff0c;未来我们将看到通过纯粹的人工智能生成的纹理和对象。他还提到&a…

构建下一代数据解决方案:SingleStore、MinIO 和现代 Datalake 堆栈

SingleStore 是专为数据密集型工作负载而设计的云原生数据库。它是一个分布式关系 SQL 数据库管理系统&#xff0c;支持 ANSI SQL&#xff0c;并因其在数据引入、事务处理和查询处理方面的速度而受到认可。SingleStore 可以存储关系、JSON、图形和时间序列数据&#xff0c;以满…

SaToken系列--指定token存储的数据源

一、背景 由于特殊需求&#xff0c;需要在一个项目里面使用两个redis数据源&#xff0c;一个是本项目A的使用的&#xff0c;另一个数据源是用于satoken使用的。相当于需要使用其他项目B的登录校验&#xff0c;因此需要接入B的satoken&#xff0c;而B的satoken使用的token存储的…

快速排序.

给定你一个长度为 n 的整数数列。 请你使用快速排序对这个数列按照从小到大进行排序。 并将排好序的数列按顺序输出。 输入格式 输入共两行&#xff0c;第一行包含整数 n 。 第二行包含 n 个整数&#xff08;所有整数均在 1∼109 范围内&#xff09;&#xff0c;表示整个数…

c#调用dll

动态调用dll 调用dll&#xff0c;通常是在c# 的项目中&#xff0c;的引用上点右键–添加引用。在弹出的窗口中点浏览&#xff0c;定位到dll上。但是c#要调用的dll要是c#写的&#xff0c;如果是c等写的&#xff0c;会提示不是有效的程序集或com组件。 因此&#xff0c;不能通过…

AI 大模型企业应用实战(13)-Lostinthemiddle长上下文精度处理

1 长文本切分信息丢失处理方案 10检索时性能大幅下降相关信息在头尾性能最高检索 ->> 排序 ->使用 实战 安装依赖&#xff1a; ! pip install sentence-transformers 演示如何使用 Langchain 库中的组件来处理长文本和检索相关信息。 导入所需的库使用指定的预训…

(4) cmake编译静态库和动态库

文章目录 静态库整体代码动态库编译整体代码执行结果(静态) 静态库整体代码 static.h #pragma onecevoid static_demo();static.cpp #include "static.h" #include <iostream>void static_demo(){std::cout<<"static demo"<<std::end…

CesiumJS【Basic】- #007 绘制直线段以避免地球曲率的影响

文章目录 绘制直线段以避免地球曲率的影响1 目标2 实现绘制直线段以避免地球曲率的影响 1 目标 绘制直线段以避免地球曲率的影响 2 实现 在CesiumJS中,直线的弯曲是由地球曲率引起的,因为地球是一个球体而不是一个平面。因此,如果您要在地球上绘制两点之间的直线,它将会…

Posix多线程编程总结

Posix在线文档&#xff1a; The Single UNIX Specification, Version 2 (opengroup.org) 本文主要参考这位大神的文章&#xff1a; Posix多线程编程学习笔记 - 凌峰布衣 - 博客园 (cnblogs.com) 线程安全问题 多线程编程中&#xff0c;经常遇到的就是线程安全问题&#xff0c;或…

PINN解偏微分方程实例4

PINN解偏微分方程实例4 一、正问题1. Diffusion equation2. Burgers’ equation3. Allen–Cahn equation4. Wave equation 二、反问题1. Burgers’ equation3. 部分代码示例 本文使用 PINN解偏微分方程实例1中展示的代码求解了以四个具体的偏微分方程&#xff0c;包括Diffusio…

第T2周:彩色图片分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 &#x1f449; 要求&#xff1a; 学习如何编写一个完整的深度学习程序了解分类彩色图片会灰度图片有什么区别测试集accuracy到达72% &#x1f9be;我的环境&am…