当前位置: 首页 > 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…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...