当前位置: 首页 > news >正文

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)}
}

相关文章:

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.apach…...

数据结构绪论

文章目录 绪论数据结构三要素算法 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;数据结构专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年12月12日01点09分 绪论 数据是信息的载体&#xff0c;描述客观事物属性的数、字符及所有能输入…...

前端开发常用四大框架学习难度咋样?

前端开发常用四大框架指的是 jQuery vue react angular ‌jQuery‌&#xff1a; ‌学习难度‌&#xff1a;相对较低‌特点‌&#xff1a;jQuery 是一个快速、小巧、功能丰富的 JavaScript 库。它使得 HTML 文档遍历和操作、事件处理、动画和 Ajax 交互更加简单。‌适用场景‌&a…...

OWASP 十大安全漏洞的原理

1. Broken Access Control&#xff08;访问控制失效&#xff09; 原理&#xff1a;应用程序未正确实施权限检查&#xff0c;导致攻击者通过篡改请求、强制浏览或权限提升等手段绕过访问控制。 攻击手段&#xff1a; 修改 URL、HTML、或 API 请求以访问未经授权的资源。 删除…...

论文 | ChunkRAG: Novel LLM-Chunk Filtering Method for RAG Systems

本文详细介绍了一种新颖的检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;系统方法——ChunkRAG&#xff0c;该方法通过对文档的分块语义分析和过滤显著提升了生成系统的准确性和可靠性。 1. 研究背景与问题 1.1 检索增强生成的意义 RAG系统结合…...

ORACLE SQL思路: 多行数据有相同字段就合并成一条数据 分页展示

数据 分数表&#xff1a; 学号&#xff0c;科目名&#xff08;A,B,C&#xff09;&#xff0c;分数 需求 分页列表展示&#xff0c; 如果一个学号的科目有相同的分数&#xff0c; 合并成一条数据&#xff0c;用 拼接 科目名 ORACLE SQL 实现 SELECT Z.*, SUBSTR(DECODE(f…...

SpringBoot 手动实现动态切换数据源 DynamicSource (中)

大家好&#xff0c;我是此林。 SpringBoot 手动实现动态切换数据源 DynamicSource &#xff08;上&#xff09;-CSDN博客 在上一篇博客中&#xff0c;我带大家手动实现了一个简易版的数据源切换实现&#xff0c;方便大家理解数据源切换的原理。今天我们来介绍一个开源的数据源…...

y3编辑器教学5:触发器2 案例演示

文章目录 一、探索1.1 ECA1.1.1 ECA的定义1.1.2 使用触发器实现瞬间移动效果 1.2 变量1.2.1 什么是变量1.2.2 使用变量存储碎片收集数量并展现 1.3 if语句&#xff08;魔法效果挂接&#xff09;1.3.1 地形设置1.3.2 编写能量灌注逻辑1.3.3 编写能量灌注后&#xff0c;实现传送逻…...

数值分析——插值法(二)

文章目录 前言一、Hermite插值1.两点三次Hermite插值2.两点三次Hermite插值的推广3.非标准型Hermite插值 二、三次样条插值1.概念2.三弯矩方程 前言 之前写过Lagrange插值与Newton插值法的内容&#xff0c;这里介绍一些其他的插值方法&#xff0c;顺便复习数值分析. 一、Hermi…...

杨振宁大学物理视频中黄色的字,c#写程序去掉

先看一下效果&#xff1a;&#xff08;还有改进的余地&#xff09; 写了个程序消除杨振宁大学物理中黄色的字 我的方法是笨方法&#xff0c;也比较刻板。 1&#xff0c;首先想到&#xff0c;把屏幕打印下来。c#提供了这样一个函数&#xff1a; Bitmap bmp new Bitmap(640, 48…...

uni-app 设置缓存过期时间【跨端开发系列】

&#x1f517; uniapp 跨端开发系列文章&#xff1a;&#x1f380;&#x1f380;&#x1f380; uni-app 组成和跨端原理 【跨端开发系列】 uni-app 各端差异注意事项 【跨端开发系列】uni-app 离线本地存储方案 【跨端开发系列】uni-app UI库、框架、组件选型指南 【跨端开…...

微信小程序base64图片与临时路径互相转换

1、base64图片转临时路径 /*** 将base64图片转临时路径* param {*} dataurl* param {*} filename* returns*/base64ImgToFile(dataurl, filename "file") {const base64 dataurl; // base64码const time new Date().getTime();const imgPath wx.env.USER_DATA_P…...

蓝桥杯刷题——day2

蓝桥杯刷题——day2 题目一题干题目解析代码 题目二题干解题思路代码 题目一 题干 三步问题。有个小孩正在上楼梯&#xff0c;楼梯有n阶台阶&#xff0c;小孩一次可以上1阶、2阶或3阶。实现一种方法&#xff0c;计算小孩有多少种上楼梯的方式。结果可能很大&#xff0c;你需要…...

5.删除链表的倒数第N个节点

19.删除链表的倒数第N个节点 题目&#xff1a; 19. 删除链表的倒数第 N 个结点 - 力扣&#xff08;LeetCode&#xff09; 分析&#xff1a; 要删除倒数第几个节点&#xff0c;那么我们需要怎么做呢&#xff1f;我们需要定义两个指针&#xff0c;快指针和慢指针&#xff0c;…...

自己总结:selenium高阶知识

全篇大概10000字&#xff08;含代码&#xff09;&#xff0c;建议阅读时间30min 一、等待机制 如果有一些内容是通过Ajax加载的内容&#xff0c;那就需要等待内容加载完毕才能进行下一步操作。 为了避免人为操作等待&#xff0c;会遇到的问题&#xff0c; selenium将等待转换…...

前端怎么预览pdf

1.背景 后台返回了一个在线的pdf地址&#xff0c;需要我这边去做一个pdf的预览&#xff08;需求1&#xff09;&#xff0c;并且支持配置是否可以下载&#xff08;需求2&#xff09;&#xff0c;需要在当前页就能预览&#xff08;需求3&#xff09;。之前我写过一篇预览pdf的文…...

activemq 的安装部署

下载 https://activemq.apache.org/components/classic/download/# 在/opt目录下载 wget https://dlcdn.apache.org//activemq/5.18.6/apache-activemq-5.18.6-bin.tar.gz解压 tar -zxvf apache-activemq-5.18.6-bin.tar.gz配置java环境 vim /opt/apache-activemq-5.18.6/b…...

【H3CNE邓方鸣】配置链路聚合+2024.12.11

文章目录 链路聚合作用负载分担分类静态聚合动态聚合 链路聚合作用 定义&#xff1a;把连接到统一交换机上的多个物理端口捆绑为一个逻辑端口 增加链路带宽&#xff1a;聚合组内只要还有物理端口存活&#xff0c;链路就不会中断 提供链路可靠性&#xff1a;避免了STP计算&…...

C语言 学习 日志 递归函数 2024/12/12

C语言 学习 日志 递归函数 介绍: 初始调用&#xff1a;递归函数被首次调用。递归调用&#xff1a;递归函数在其定义中调用自身&#xff0c;创建新的栈帧。基本情况检查&#xff1a;每次递归调用时&#xff0c;检查是否满足基本情况。如果满足&#xff0c;返回结果并开始回溯。…...

【Ubuntu】使用ip link工具创建虚拟局域网并配置?

&#x1f98b;&#x1f98b;&#x1f98b;如何使用ip link工具创建虚拟局域网&#xff1f; sudo ip link add link enx888bd66b7000 name enx.120 type vlan id 120 上述命令使用ip link工具在Linux系统中创建了一个新的虚拟局域网&#xff08;VLAN&#xff09;接口&#xff0c…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...

MyBatis中关于缓存的理解

MyBatis缓存 MyBatis系统当中默认定义两级缓存&#xff1a;一级缓存、二级缓存 默认情况下&#xff0c;只有一级缓存开启&#xff08;sqlSession级别的缓存&#xff09;二级缓存需要手动开启配置&#xff0c;需要局域namespace级别的缓存 一级缓存&#xff08;本地缓存&#…...