go高性能单机缓存项目

代码

// Copyright 2021 ByteDance Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.package asynccacheimport ("fmt""log""sync""sync/atomic""time"sf "golang.org/x/sync/singleflight"
)// Options controls the behavior of AsyncCache.
type Options struct {RefreshDuration time.DurationFetcher         func(key string) (interface{}, error)// If EnableExpire is true, ExpireDuration MUST be set.EnableExpire   boolExpireDuration time.DurationErrorHandler  func(key string, err error)ChangeHandler func(key string, oldData, newData interface{})DeleteHandler func(key string, oldData interface{})IsSame     func(key string, oldData, newData interface{}) boolErrLogFunc func(str string)
}// AsyncCache .
type AsyncCache interface {// SetDefault sets the default value of given key if it is new to the cache.// It is useful for cache warming up.// Param val should not be nil.SetDefault(key string, val interface{}) (exist bool)// Get tries to fetch a value corresponding to the given key from the cache.// If error occurs during the first time fetching, it will be cached until the// sequential fetching triggered by the refresh goroutine succeed.Get(key string) (val interface{}, err error)// GetOrSet tries to fetch a value corresponding to the given key from the cache.// If the key is not yet cached or error occurs, the default value will be set.GetOrSet(key string, defaultVal interface{}) (val interface{})// Dump dumps all cache entries.// This will not cause expire to refresh.Dump() map[string]interface{}// DeleteIf deletes cached entries that match the `shouldDelete` predicate.DeleteIf(shouldDelete func(key string) bool)// Close closes the async cache.// This should be called when the cache is no longer needed, or may lead to resource leak.Close()
}// asyncCache .
type asyncCache struct {sfg  sf.Groupopt  Optionsdata sync.Map
}type tickerType intconst (refreshTicker tickerType = iotaexpireTicker
)type sharedTicker struct {sync.Mutexstarted  boolstopChan chan boolticker   *time.Tickercaches   map[*asyncCache]struct{}
}var (// 共用 tickerrefreshTickerMap, expireTickerMap sync.Map
)type entry struct {val    atomic.Valueexpire int32 // 0 means useful, 1 will expireerr    Error
}func (e *entry) Store(x interface{}, err error) {if x != nil {e.val.Store(x)} else {e.val = atomic.Value{}}e.err.Store(err)
}func (e *entry) Touch() {atomic.StoreInt32(&e.expire, 0)
}// NewAsyncCache creates an AsyncCache.
func NewAsyncCache(opt Options) AsyncCache {c := &asyncCache{sfg: sf.Group{},opt: opt,}if c.opt.ErrLogFunc == nil {c.opt.ErrLogFunc = func(str string) {log.Println(str)}}if c.opt.EnableExpire {if c.opt.ExpireDuration == 0 {panic("asynccache: invalid ExpireDuration")}ti, _ := expireTickerMap.LoadOrStore(c.opt.ExpireDuration,&sharedTicker{caches: make(map[*asyncCache]struct{}), stopChan: make(chan bool, 1)})et := ti.(*sharedTicker)et.Lock()et.caches[c] = struct{}{}if !et.started {et.started = trueet.ticker = time.NewTicker(c.opt.ExpireDuration)go et.tick(et.ticker, expireTicker)}et.Unlock()}ti, _ := refreshTickerMap.LoadOrStore(c.opt.RefreshDuration,&sharedTicker{caches: make(map[*asyncCache]struct{}), stopChan: make(chan bool, 1)})rt := ti.(*sharedTicker)rt.Lock()rt.caches[c] = struct{}{}if !rt.started {rt.started = truert.ticker = time.NewTicker(c.opt.RefreshDuration)go rt.tick(rt.ticker, refreshTicker)}rt.Unlock()return c
}// SetDefault sets the default value of given key if it is new to the cache.
func (c *asyncCache) SetDefault(key string, val interface{}) bool {ety := &entry{}ety.Store(val, nil)actual, exist := c.data.LoadOrStore(key, ety)if exist {actual.(*entry).Touch()}return exist
}// Get tries to fetch a value corresponding to the given key from the cache.
// If error occurs during in the first time fetching, it will be cached until the
// sequential fetchings triggered by the refresh goroutine succeed.
func (c *asyncCache) Get(key string) (val interface{}, err error) {var ok boolval, ok = c.data.Load(key)if ok {e := val.(*entry)e.Touch()return e.val.Load(), e.err.Load()}val, err, _ = c.sfg.Do(key, func() (v interface{}, e error) {v, e = c.opt.Fetcher(key)ety := &entry{}ety.Store(v, e)c.data.Store(key, ety)return})return
}// GetOrSet tries to fetch a value corresponding to the given key from the cache.
// If the key is not yet cached or fetching failed, the default value will be set.
func (c *asyncCache) GetOrSet(key string, def interface{}) (val interface{}) {if v, ok := c.data.Load(key); ok {e := v.(*entry)if e.err.Load() != nil {ety := &entry{}ety.Store(def, nil)c.data.Store(key, ety)return def}e.Touch()return e.val.Load()}val, _, _ = c.sfg.Do(key, func() (interface{}, error) {v, e := c.opt.Fetcher(key)if e != nil {v = def}ety := &entry{}ety.Store(v, nil)c.data.Store(key, ety)return v, nil})return
}// Dump dumps all cached entries.
func (c *asyncCache) Dump() map[string]interface{} {data := make(map[string]interface{})c.data.Range(func(key, val interface{}) bool {k, ok := key.(string)if !ok {c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k))c.data.Delete(key)return true}data[k] = val.(*entry).val.Load()return true})return data
}// DeleteIf deletes cached entries that match the `shouldDelete` predicate.
func (c *asyncCache) DeleteIf(shouldDelete func(key string) bool) {c.data.Range(func(key, value interface{}) bool {s := key.(string)if shouldDelete(s) {if c.opt.DeleteHandler != nil {go c.opt.DeleteHandler(s, value)}c.data.Delete(key)}return true})
}// Close stops the background goroutine.
func (c *asyncCache) Close() {// close refresh tickerti, _ := refreshTickerMap.Load(c.opt.RefreshDuration)rt := ti.(*sharedTicker)rt.Lock()delete(rt.caches, c)if len(rt.caches) == 0 {rt.stopChan <- truert.started = false}rt.Unlock()if c.opt.EnableExpire {// close expire tickerti, _ := expireTickerMap.Load(c.opt.ExpireDuration)et := ti.(*sharedTicker)et.Lock()delete(et.caches, c)if len(et.caches) == 0 {et.stopChan <- trueet.started = false}et.Unlock()}
}// tick .
// pass ticker but not use t.ticker directly is to ignore race.
func (t *sharedTicker) tick(ticker *time.Ticker, tt tickerType) {var wg sync.WaitGroupdefer ticker.Stop()for {select {case <-ticker.C:t.Lock()for c := range t.caches {wg.Add(1)go func(c *asyncCache) {defer wg.Done()if tt == expireTicker {c.expire()} else {c.refresh()}}(c)}wg.Wait()t.Unlock()case stop := <-t.stopChan:if stop {return}}}
}func (c *asyncCache) expire() {c.data.Range(func(key, value interface{}) bool {k, ok := key.(string)if !ok {c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k))c.data.Delete(key)return true}e, ok := value.(*entry)if !ok {c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not entry", k, value))c.data.Delete(key)return true}if !atomic.CompareAndSwapInt32(&e.expire, 0, 1) {if c.opt.DeleteHandler != nil {go c.opt.DeleteHandler(k, value)}c.data.Delete(key)}return true})
}func (c *asyncCache) refresh() {c.data.Range(func(key, value interface{}) bool {k, ok := key.(string)if !ok {c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not string", k, k))c.data.Delete(key)return true}e, ok := value.(*entry)if !ok {c.opt.ErrLogFunc(fmt.Sprintf("invalid key: %v, type: %T is not entry", k, value))c.data.Delete(key)return true}newVal, err := c.opt.Fetcher(k)if err != nil {if c.opt.ErrorHandler != nil {go c.opt.ErrorHandler(k, err)}if e.err.Load() != nil {e.err.Store(err)}return true}if c.opt.IsSame != nil && !c.opt.IsSame(k, e.val.Load(), newVal) {if c.opt.ChangeHandler != nil {go c.opt.ChangeHandler(k, e.val.Load(), newVal)}}e.Store(newVal, err)return true})
}

流程图

设置过期定时器
设置刷新定时器
有错误
无错误
开始
NewAsyncCache 初始化 AsyncCache 实例
设置过期定时器
设置刷新定时器
返回 AsyncCache 实例
SetDefault 设置默认值
为给定的键值对中的值设定默认值
如果在缓存池中就刷新键值对过期时间
Get 获取缓存
GetOrSet 获取或设置缓存
Dump 转储缓存
DeleteIf 删除缓存
关闭 AsyncCache
缓存存在?
加载缓存值
触发 Fetcher 获取值
singleflight 处理请求
存储或更新缓存
缓存存在?
检查缓存错误
设置默认值
加载缓存值
设置默认值
Range 遍历缓存
将缓存存到另一个图中
Range 遍历缓存
满足删除条件?
删除缓存项
EnableExpire?
停止过期定时器
停止刷新定时器
资源清理

其中的refreshTickerMap, expireTickerMap存放的是每个特定的刷新时间/过期时间对应的sharedTicker
每个sharedTicker负责多个相同刷新时间/过期时间的缓存池的更新/过期操作

测试代码

package mainimport ("asynccache/asynccache""fmt""log""time"
)// 模拟一个简单的数据获取函数
func simpleFetcher(key string) (interface{}, error) {log.Printf("Fetching data for key: %s\n", key)time.Sleep(500 * time.Millisecond) // 模拟数据获取的延迟return fmt.Sprintf("value_for_%s", key), nil
}// 打印缓存中所有的数据观察
func showAllCacheData(cache asynccache.AsyncCache) {cacheData := cache.Dump() // 导出cache数据// cacheData map[string]interface{} 类型为interface{},代表任意类型for k, v := range cacheData {// %s代表匹配字符串,%+v代表构造任意类型log.Printf("Fetching data for key: %s, value: %+v", k, v)}
}func main() {// 创建一个 AsyncCache 实例cache := asynccache.NewAsyncCache(asynccache.Options{RefreshDuration: 2 * time.Second, // 每2秒刷新一次Fetcher:         simpleFetcher,EnableExpire:    true,ExpireDuration:  5 * time.Second, // 每5秒过期一次ErrorHandler: func(key string, err error) {log.Printf("Error fetching key %s: %v\n", key, err)},ChangeHandler: func(key string, oldData, newData interface{}) {log.Printf("Key %s changed from %v to %v\n", key, oldData, newData)},DeleteHandler: func(key string, oldData interface{}) {log.Printf("Key %s expired with value %v\n", key, oldData)},})// 设置默认值cache.SetDefault("key1", "default_value_for_key1")// 观察缓存数据showAllCacheData(cache)// 获取值val, err := cache.Get("key1")if err != nil {log.Printf("Error getting key1: %v\n", err)} else {log.Printf("Got value for key1: %v\n", val)}// 使用 GetOrSetval = cache.GetOrSet("key2", "default_value_for_key2")log.Printf("Got value for key2: %v\n", val)// 等待刷新和过期time.Sleep(6 * time.Second)// 再次获取值val, err = cache.Get("key1")if err != nil {log.Printf("Error getting key1 after refresh: %v\n", err)} else {log.Printf("Got value for key1 after refresh: %v\n", val)}// 删除特定的缓存项cache.DeleteIf(func(key string) bool {return key == "key2"})// 关闭缓存cache.Close()// 尝试获取值,应该会失败val, err = cache.Get("key1")if err != nil {log.Printf("Error getting key1 after close: %v\n", err)} else {log.Printf("Got value for key1 after close: %v\n", val)}
}

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

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

相关文章

ichunqiu-2024年春秋杯网络安全联赛夏季赛-brother

1.打开题目&#xff0c;看到题目我就想到了再后面加一个ls&#xff0c;结果回显了ls&#xff0c;然后又想到会不会是模板注入&#xff0c;尝试{{7*7}}&#xff0c;然后页面返回了49&#xff0c;说明存在模板注入 如下&#xff0c;判定为模板注入 看一下系统环境配置 然后看可…

基于Qwen2-VL模型针对LaTeX OCR任务进行微调训练 - 数据处理

基于Qwen2-VL模型针对LaTeX OCR任务进行微调训练 - 数据处理 flyfish 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_LoRA配置如何写 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_单图推理 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_原模型_单图推理 基于Q…

QT从入门到精通——Qlabel介绍与使用

1. QT介绍——代码测试 Qt 是一个跨平台的应用程序开发框架&#xff0c;广泛用于开发图形用户界面&#xff08;GUI&#xff09;应用程序&#xff0c;也支持非图形应用程序的开发。Qt 提供了一套工具和库&#xff0c;使得开发者能够高效地构建高性能、可移植的应用程序。以下是…

Edge SCDN深度解析,边缘安全加速的创新实践

边缘安全加速&#xff08;Edge Secure Content Delivery Network&#xff0c;SCDN&#xff09;是酷盾安全推出的边缘集分布式 DDoS 防护、CC 防护、WAF 防护、BOT 行为分析为一体的安全加速解决方案。通过边缘缓存技术&#xff0c;智能调度使用户就近获取所需内容&#xff0c;为…

WebRTC Simulcast 大小流介绍与优化实践

Simulcast 是 WebRTC 中的一种标准化技术 &#xff0c;简称大小流。通过 Simulcast&#xff0c;客户端可以同时发送同一视频的多个版本。每个版本都以不同的分辨率和帧率独立编码&#xff0c;带宽较多的拉流端可以接收较高质量的视频流&#xff0c;带宽有限的拉流端则可以接收较…

40分钟学 Go 语言高并发:服务监控与追踪

服务监控与追踪 一、知识要点总览 模块核心内容技术选型难度监控指标请求量、响应时间、错误率、资源使用Prometheus Grafana中链路追踪分布式调用链、性能瓶颈分析Jaeger, OpenTelemetry高日志处理日志收集、分析、存储ELK Stack中告警系统告警规则、通知渠道、告警分级Ale…

vue3 使用 konva

1&#xff1a;安装 npm install vue-konva konva --save 在main.ts 里面引入 import VueKonva from vue-konva; app.use(VueKonva); 2&#xff1a;效果图 3&#xff1a;直接粘贴复制就能用你 &#xff08;重要的地方做了备注&#xff09; <template><div st…

【MySQL 进阶之路】了解 性能优化 与 设计原则

1.B树的优势 “矮胖”结构&#xff1a; 矮&#xff1a;B树的每个节点存储更多的关键字&#xff0c;从而减少了树的层级&#xff08;最多三层&#xff09;&#xff0c;减少了磁盘I/O操作&#xff0c;提高了查询效率。胖&#xff1a;叶子节点存储实际的数据&#xff0c;并使用双…

Spring Boot集成Knife4j文档工具

Knife4j 搭建 Knife4j环境的的搭建和Swagger一样都比较简单&#xff0c;只需要极简的配置即可。 maven依赖 我使用的是较高版本的基于openapi规范的依赖包&#xff0c;OpenAPI2(Swagger)规范是Knife4j之前一直提供支持的版本&#xff0c;底层依赖框架为Springfox。 此次在4…

LightRAG测试BUG

错误一&#xff1a; LightRAG无法回答错误&#xff1a; INFO:lightrag:kw_prompt result:{{"high_level_keywords": ["xxx", "xxx"],"low_level_keywords": ["xxx", "xxx", "xxx"] }} JSON parsing e…

keil5搜索框还有左侧文件状态栏不见的问题

点击上面的window&#xff0c;弹出 reset view to default &#xff0c;然后点击&#xff0c;再点击reset&#xff0c;就ok了

基于Mybatis,MybatisPlus实现数据库查询分页功能

基于Mybatis&#xff0c;MybatisPlus实现数据库查询分页功能 目录 基于Mybatis&#xff0c;MybatisPlus实现数据库查询分页功能使用Mybatis插件实现分页数据库准备分页插件配置和使用常用数据&#xff1a; 使用MybatisPlus插件实现分页数据库准备分页插件配置和使用自定义分页查…

python脚本将多个txt文件的内容合并为一个

以下是一个简单的 Python 脚本&#xff0c;可以将指定目录中的多个 .txt 文件的内容合并到一个文件中&#xff1a; 脚本代码 import osdef merge_txt_files(input_dir, output_file):"""将指定目录中的所有txt文件合并到一个文件中。:param input_dir: 包含tx…

HNU_多传感器(专选)_作业4(构建单层感知器实现分类)

1. (论述题)&#xff08;共1题&#xff0c;100分&#xff09; 假设平面坐标系上有四个点&#xff0c;要求构建单层感知器实现分类。 (3,3),(4,3) 两个点的标签为1&#xff1b; (1,1),(0,2) 两个点的标签为-1。 思路&#xff1a;要分类的数据是2维数据&#xff0c;需要2个输入…

内圆弧转子泵绘制工具开发

接着上期的Gerotor 泵的话题继续。最近有小伙伴找我开发一个内圆弧摆线泵的计算绘制工具&#xff0c;也就是把上次计算绘制的过程做成一个桌面应用工具&#xff0c;这样用起来会更方便、效率更高。那究竟是什么样的工具呢&#xff1f;一起来看看&#xff1a; 前面不是已经有了上…

面试技术点之安卓篇

一、基础 二、高级 三、组件 Android中SurfaceView和TextureView有什么区别&#xff1f; 参考 Android中SurfaceView和TextureView有什么区别&#xff1f; 四、三方框架 五、系统源码 六、性能优化

【汇编】思考汇编中的两个基本问题

1. 若干年前的疑问 几年前还在大学学习汇编时&#xff0c;不管是考试还是课程设计&#xff0c;其实都很顺利。但是心里一直对什么时候使用哪个寄存器存在疑惑&#xff0c;编写汇编时&#xff0c;没有十足的把握&#xff0c;都是抱着试一试的心态去完成了课程任务。 工作八年有…

在Ubuntu 2404上使用最新的PicGo

在转向Ubuntu之后&#xff0c;果断下载了今年最新的Ubuntu2404,但是随之而来的是底层组件的更新&#xff0c;很多以前可以畅快使用的软件&#xff0c;因为需要老版本的组件而不能正确运行&#xff0c;PicGo就是如此 我们从这里打开Release列表 其中Ubuntu可用的只有这个AppIma…

用ue5打开网址链接

需要用到 Launch URL 这个函数 字面意思就是打开填写的链接网页 这里填写的是百度&#xff0c;按下Tab键后就会打开百度的网页

ISP(Image Signal Processor)——HDR技术总结

传统多帧融合技术 拍摄一系列不同曝光时长的图像帧&#xff08;LDR&#xff09;&#xff0c;然后使用融合算法进行融合成HDR图像。 融合算法可以分为两种 基于照度图估计的融合 基于照度估计需要拟合相机响应函数&#xff0c;详细可以参考如下论文&#xff1a; Recovering H…