Golang项目:实现一个内存缓存系统

要求

  1. 支持设定过期时间,精确到秒
  2. 支持设定最大内存,当内存超过时做出合适的处理
  3. 支持并发安全
  4. 按照以下接口安全

type Cache interface{//size : 1KB 100KB 1MB 2MB 1GBSetMaxMemory(size string )bool//将value写入缓存Set(key string, val interface{},expire time.Duration)bool//根据key值获取valueGet(key string )(interface{},bool)//删除keyDel(key string)bool//判断key是否存在Exists(key string)bool//清空所有keyFlush()bool//获取缓存中所有key的数量Keys()int64
}
  1. 使用示例
cache := NewMemCache()
cache.SetMaxMemory("100MB")
cache.Set("int",1)
cache.Set("bool",false)
cache.Set("data",map[string]interface(){"a":1})
cache.Get("int")
cache.Del("int")
cache.Flush()
cache.Keys()

首先创建对应文件夹
在这里插入图片描述

其中main.go中填入测试案例

package mainimport ("memCache/cache""time"
)func main() {cache := cache.NewMemCache()cache.SetMaxMemory("200MB")cache.Set("int", 1, time.Second)cache.Set("bool", false, time.Second)cache.Set("data", map[string]interface{}{"a": 1}, time.Second)//cache.Set("int",1)//cache.Set("bool",false)//cache.Set("data",map[string]interface{}{"a":1})cache.Get("int")cache.Del("int")cache.Flush()cache.Keys()//num, str := cache.ParseSize("2KB")//fmt.Println(num, str)}

定义cache.go的接口

package cacheimport "time"type Cache interface {//size : 1KB 100KB 1MB 2MB 1GBSetMaxMemory(size string) bool//将value写入缓存Set(key string, val interface{}, expire time.Duration) bool//根据key值获取valueGet(key string) (interface{}, bool)//删除keyDel(key string) bool//判断key是否存在Exists(key string) bool//清空所有keyFlush() bool//获取缓存中所有key的数量Keys() int64
}

然后在memCache.go中实现

package cacheimport ("fmt""time"
)type memCache struct {//最大内存 -- 单位字节maxMemorySize int64//最大内存字符串表示maxMemorySizeStr string//当前内存大小 -- 单位字节currentMemorySize int64
}func NewMemCache() Cache {return &memCache{}
}// size : 1KB 100KB 1MB 2MB 1GB
func (mc *memCache) SetMaxMemory(size string) bool {mc.maxMemorySize, mc.maxMemorySizeStr = ParseSize(size)fmt.Println(mc.maxMemorySize, mc.maxMemorySizeStr)fmt.Println("called set Maxmem")return false
}// 将value写入缓存
func (mc *memCache) Set(key string, val interface{}, expire time.Duration) bool {fmt.Println("called set")return false
}// 根据key值获取value
func (mc *memCache) Get(key string) (interface{}, bool) {fmt.Println("called get")return nil, false
}// 删除key
func (mc *memCache) Del(key string) bool {fmt.Println("called del")return false
}// 判断key是否存在
func (mc *memCache) Exists(key string) bool {return false
}// 清空所有key
func (mc *memCache) Flush() bool {return false
}// 获取缓存中所有key的数量
func (mc *memCache) Keys() int64 {return 0
}

实现设置最大内存数中

// size : 1KB 100KB 1MB 2MB 1GB
func (mc *memCache) SetMaxMemory(size string) bool {mc.maxMemorySize, mc.maxMemorySizeStr = ParseSize(size)fmt.Println(mc.maxMemorySize, mc.maxMemorySizeStr)fmt.Println("called set Maxmem")return false
}

ParseSize()方法在util.go文件中实现


const (B = 1 << (iota * 10)KBMBGBTBPB
)func ParseSize(size string) (int64, string) {//默认大小为 100MBre, _ := regexp.Compile("[0-9]+")//fmt.Println(re)unit := string(re.ReplaceAll([]byte(size), []byte("")))//fmt.Println("unit: " + unit)num, _ := strconv.ParseInt(strings.Replace(size, unit, "", 1), 10, 64)//fmt.Println("num: ", num)unit = strings.ToUpper(unit)var byteNum int64 = 0switch unit {case "B":byteNum = numcase "KB":byteNum = num * KBcase "MB":byteNum = num * MBcase "GB":byteNum = num * GBcase "TB":byteNum = num * TBcase "PB":byteNum = num * PBdefault:byteNum = 0num = 0}if num == 0 {log.Println("ParseSize 仅支持 B,KB,MB,GB,TB,PB")num = 100byteNum = num * MBunit = "MB"}sizeStr := strconv.FormatInt(num, 10) + unitreturn byteNum, sizeStr
}

初步测试

在这里插入图片描述
说明ParseSize实现无误


接下来是实现Set方法

在memCache.go中添加


type memCacheValue struct {//value值val interface{}//过期时间expiration time.Time//value 大小size int64
}

来存储每一块内存的信息,包括大小,值,过期时间

对于Set操作,我们需要创建一些方法来辅助执行
如:get,add,del操作

// 将value写入缓存
func (mc *memCache) Set(key string, val interface{}, expire time.Duration) bool {mc.locker.Lock()defer mc.locker.Unlock()v := &memCacheValue{val:        val,expireTime: time.Now().Add(expire),size:       GetValSize(val),}mc.del(key)mc.add(key, v)if mc.currentMemorySize > mc.maxMemorySize {mc.del(key)panic(fmt.Sprintf("max memory size %s", mc.maxMemorySize))}return false
}func (mc *memCache)del(key string) {tmp,ok:=mc.get(key)if ok && tmp != nil {mc.currentMemorySize -= tmp.sizedelete(mc.values, key)}}func (mc *memCache)add(key string, val *memCacheValue)  {mc.values[key] = valmc.currentMemorySize += val.size}func (mc *memCache)get(key string) (*memCacheValue,bool) {val,ok := mc.values[key]return val,ok
}

同理也可以利用上面创建的add,del,get方法来实现Get操作


// 根据key值获取value
func (mc *memCache) Get(key string) (interface{}, bool) {mc.locker.RLock()defer mc.locker.RUnlock()mcv, ok := mc.get(key)if ok{//判断缓存是否过期if mcv.expireTime.Before(time.Now()) {mc.del(key)return nil, false}return mcv.val, true}fmt.Println("called get")return nil, false
}

Del操作的实现


// 删除key
func (mc *memCache) Del(key string) bool {mc.locker.Lock()defer mc.locker.Unlock()mc.del(key)fmt.Println("called del")return false
}

剩余其他操作

// 判断key是否存在
func (mc *memCache) Exists(key string) bool {mc.locker.RLock()defer mc.locker.RUnlock()_,ok := mc.values[key]return ok
}// 清空所有key
func (mc *memCache) Flush() bool {mc.locker.Lock()defer mc.locker.Unlock()mc.values = make(map[string]*memCacheValue)mc.currentMemorySize = 0return false
}// 获取缓存中所有key的数量
func (mc *memCache) Keys() int64 {mc.locker.RLock()defer mc.locker.RUnlock()return int64(len(mc.values))
}

现在的问题是,我们只是设置了,在过期之后,就不能访问了,但是实际上还占用着缓存,只有在再一次Get的时候,发现过期了,才会删除掉

所以现在我们做一个定期清空的轮询访问


// 定期清除缓存
func (mc *memCache) clearExpiredItem() {timeTicker := time.NewTicker(mc.clearExpiredItemTimeInerval)defer timeTicker.Stop()for {fmt.Println("轮询检查")select {case <-timeTicker.C:for key, item := range mc.values {if item.expireTime.Before(time.Now()) {mc.locker.Lock()mc.del(key)mc.locker.Unlock()fmt.Println("check")}}}}
}

测试案例

之后,我们创建一个代理层,带使用测试案例

在这里插入图片描述

在cache-server中的cache.go文件中

创建对象,添加代理

package cache_serverimport ("memCache/cache""time"
)type cacheServer struct {memCache cache.Cache
}func NewMemCache() *cacheServer {return &cacheServer{memCache: cache.NewMemCache(),}
}// size : 1KB 100KB 1MB 2MB 1GB
func (cs *cacheServer) SetMaxMemory(size string) bool {return cs.memCache.SetMaxMemory(size)
}// 将value写入缓存
func (cs *cacheServer) Set(key string, val interface{}, expire ...time.Duration) bool {expireTs := time.Second * 0if len(expire) > 0 {expireTs = expire[0]}return cs.memCache.Set(key, val, expireTs)
}// 根据key值获取value
func (cs *cacheServer) Get(key string) (interface{}, bool) {return cs.memCache.Get(key)
}// 删除key
func (cs *cacheServer) Del(key string) bool {return cs.memCache.Del(key)
}// 判断key是否存在
func (cs *cacheServer) Exists(key string) bool {return cs.memCache.Exists(key)
}// 清空所有key
func (cs *cacheServer) Flush() bool {return cs.memCache.Flush()
}// 获取缓存中所有key的数量
func (cs *cacheServer) Keys() int64 {return cs.memCache.Keys()
}

可以注意到,我们在代理层中对Set方法进行了可变长参数化

使得我们的Set方法的time部分参数为可选择填写0-n个参数,但我们只使用第一个expire[0]作为我们使用的参数

此时main函数可改成测试案例

cache := cache_server.NewMemCache()cache.SetMaxMemory("200MB")cache.Set("int", 1, 20*time.Second)cache.Set("bool", false, 10*time.Second)cache.Set("data", map[string]interface{}{"a": 1}, time.Second)cache.Set("int", 1)cache.Set("bool", false)cache.Set("data", map[string]interface{}{"a": 1})cache.Get("int")cache.Del("int")cache.Flush()cache.Keys()time.Sleep(time.Second * 25)

GetValSize函数

我们使用GetValSize函数

func GetValSize(val interface{}) int64 {size := unsafe.Sizeof(val)fmt.Println(int64(size))return int64(size)
}
cache.GetValSize(1)cache.GetValSize(false)cache.GetValSize("adwaeqweqwr")cache.GetValSize(map[string]string{"a": "b","c": "d",})

会发现
在这里插入图片描述
无论输入什么,size都是16
unsafe.Sizeof 来获取一个接口值(interface{})的大小时,它实际上返回的是接口结构体本身的大小,而不是接口中存储的具体值的大小

我们可以利用json序列化,来直接获得序列长度来代表val的大小

func GetValSize(val interface{}) int64 {byte, _ := json.Marshal(val)size := int64(len(byte))fmt.Println(size)return int64(size)
}

可以看出,我们就可以得到实际的一个大小
在这里插入图片描述


加分项,单元测试

我们在cache目录中,新建一个单元测试,memCache_test.go

package cacheimport ("testing""time"
)func TestCacheOP(t *testing.T) {testData := []struct {key    stringval    interface{}expire time.Duration}{{"slawe", 234623, time.Second * 10},{"sawe", false, time.Second * 11},{"serytje", true, time.Second * 12},{"w35wyhe", map[string]interface{}{"a": 2, "B": false}, time.Second * 13},{"swetwgb", "fiyu85", time.Second * 14},}c := NewMemCache()c.SetMaxMemory("10MB")for _, item := range testData {c.Set(item.key, item.val, item.expire)val, ok := c.Get(item.key)if !ok {t.Error("缓存取值失败")}if item.key != "w35wyhe" && val != item.val {t.Error("缓存取值数据与预期不一致")}_, ok1 := val.(map[string]interface{})if item.key == "w35wyhe" && !ok1 {t.Error("map缓存取值数据与预期不一致")}}if int64(len(testData)) != c.Keys() {t.Error("缓存数量不一致")}c.Del(testData[0].key)c.Del(testData[1].key)if int64(len(testData)) != c.Keys()+2 {t.Error("缓存数量不一致")}time.Sleep(17 * time.Second)if c.Keys() != 0 {t.Error("缓存未清空")}}

在这里插入图片描述
单元测试通过!!!

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

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

相关文章

计算机网络复习笔记(湖科大教书匠)

课程链接&#xff1a;【计算机网络微课堂&#xff08;有字幕无背景音乐版&#xff09;】 https://www.bilibili.com/video/BV1c4411d7jb/?p61&share_sourcecopy_web&vd_sourcecd12864239c2976e9f2bce4b307393f0 一、基础概念 信息交换方式 电路交换 电话交换机接通…

输入三个整数x,y,z,请把这三个数由小到大输出。-多语言实现

目录 C 语言实现 Python 实现 Java 实现 Js 实现 题目&#xff1a;输入三个整数x,y,z&#xff0c;请把这三个数由小到大输出。 程序分析&#xff1a;我们想办法把最小的数放到x上&#xff0c;先将x与y进行比较&#xff0c;如果x>y则将x与y的值进行交换&#xff0c;然后…

自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例

Kafka&#xff1a;分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析&#xff1a;从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析&#xff1a…

SparkContext讲解

SparkContext讲解 什么是 SparkContext&#xff1f; SparkContext 是 Spark 应用程序的入口点&#xff0c;是 Spark 的核心组件之一。每个 Spark 应用程序启动时&#xff0c;都会创建一个 SparkContext 对象&#xff0c;它负责与集群管理器&#xff08;如 YARN、Mesos 或 Spa…

vue项目添加骨架屏vue-skeleton-webpack-plugin,通过app.vue添加骨架屏,解决衔接空白问题

安装插件 yarn add vue-skeleton-webpack-plugin在 webpack 中引入插件&#xff1a;以4版本为例配置如下 vue.config.js plugins: [new SkeletonWebpackPlugin({webpackConfig: {entry: {app: path.join(__dirname, ./src/components/entry-skeleton.js),},},minimize: true,…

详细介绍下oracle冷备(coolbackup)

冷备&#xff0c;也就说数据库不是运行&#xff08;热的状态&#xff09;的备份。有些时候我们的数据库比较小&#xff0c;进行同操作系统数据迁移和恢复的时候就比较好用。下面我们详细介绍下oracle数据库的冷备&#xff08;我们使用最简单的拷贝数据文件方式进行冷备&#xf…

C语言菜鸟入门·关键字·int的用法

目录 1. int关键字 1.1 取值范围 1.2 符号类型 1.3 运算 1.3.1 加法运算() 1.3.2 减法运算(-) 1.3.3 乘法运算(*) 1.3.4 除法运算(/) 1.3.5 取余运算(%) 1.3.6 自增()与自减(--) 1.3.7 位运算 2. 更多关键字 1. int关键字 int 是一个关键字&#xff0…

神经网络(系统性学习三):多层感知机(MLP)

相关文章&#xff1a; 神经网络中常用的激活函数 神经网络&#xff08;系统性学习一&#xff09;&#xff1a;入门篇 神经网络&#xff08;系统性学习二&#xff09;&#xff1a;单层神经网络&#xff08;感知机&#xff09; 多层感知机&#xff08;MLP&#xff09; 多层感…

Vue——响应式数据,v-on,v-bind,v-if,v-for(内含项目实战)

目录 响应式数据 ref reactive 事件绑定指令 v-on v-on 鼠标监听事件 v-on 键盘监听事件 v-on 简写形式 属性动态化指令 v-bind iuput标签动态属性绑定 img标签动态属性绑定 b标签动态属性绑定 v-bind 简写形式 条件渲染指令 v-if 遍历指令 v-for 遍历对象的值 遍历…

鸿蒙开发Hvigor插件动态生成代码

Hvigor允许开发者实现自己的插件&#xff0c;开发者可以定义自己的构建逻辑&#xff0c;并与他人共享。Hvigor主要提供了两种方式来实现插件&#xff1a;基于hvigorfile脚本开发插件、基于typescript项目开发。下面以基于hvigorfile脚本开发插件进行介绍。 基于hvigorfile脚本…

蓝桥杯c++算法秒杀【6】之动态规划【上】(数字三角形、砝码称重(背包问题)、括号序列、组合数问题:::非常典型的必刷例题!!!)

下将以括号序列、组合数问题超级吧难的题为例子讲解动态规划 别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01;! ! ! ! &#xff01; 关注博主&#xff0c;更多蓝桥杯nice题目静待更新:) 动态规划 一、数字三角形 【问题描述】 上图给出了一…

YOLO-FaceV2: A Scale and Occlusion Aware Face Detector

《YOLO-FaceV2:一种尺度与遮挡感知的人脸检测器》 1.引言2.相关工作3.YOLO-FaceV23.1网络结构3.2尺度感知RFE模型3.3遮挡感知排斥损失3.4遮挡感知注意力网络3.5样本加权函数3.6Anchor设计策略3.7 归一化高斯Wasserstein距离 4.实验4.1 数据集4.2 训练4.3 消融实验4.3.1 SEAM块4…

Python 虚拟环境使用指南

Python 虚拟环境使用指南 博客 一、创建虚拟环境 在开始使用Python虚拟环境之前&#xff0c;我们需要先创建一个新的虚拟环境。 1. 基础创建流程 首先&#xff0c;进入您的项目目录&#xff1a; cd path\to\your\project然后使用以下命令创建虚拟环境&#xff1a; pytho…

【SQL Server】华中农业大学空间数据库实验报告 实验三 数据操作

1.实验目的 熟悉了解掌握SQL Server软件的基本操作与使用方法&#xff0c;以及通过理论课学习与实验参考书的帮助&#xff0c;熟练掌握使用T-SQL语句和交互式方法对数据表进行插入数据、修改数据、删除数据等等的操作&#xff1b;作为后续实验的基础&#xff0c;根据实验要求重…

深入解析自校正控制(STC)算法及python实现

目录 深入解析自校正控制(STC)算法第一部分:自校正控制算法概述1.1 什么是自校正控制1.2 自校正控制的核心思想1.3 STC 的应用场景1.4 STC 的分类第二部分:自校正控制算法的数学基础2.1 动态系统模型2.2 参数辨识方法2.3 控制器设计2.4 稳定性分析第三部分:Python 实现自校…

Linux 正则表达式(basic and extened)

正则表达式(Regular Expressions)&#xff0c;整理自&#xff1a; https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html gred sed 定义 Regular Expressions (REs) provide a mechanism to select specific strings from a set of character strings.…

【创建型设计模式】单例模式

【创建型设计模式】单例模式 这篇博客接下来几篇都将阐述设计模式相关内容。 接下来的顺序大概是&#xff1a;单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。 一、什么是单例模式 单例模式是一种创建型设计模式&#xff0c;它保证一个类仅有一个实例&#…

【Elasticsearch入门到落地】2、正向索引和倒排索引

接上篇《1、初识Elasticsearch》 上一篇我们学习了什么是Elasticsearch&#xff0c;以及Elastic stack(ELK)技术栈介绍。本篇我们来什么是正向索引和倒排索引&#xff0c;这是了解Elasticsearch底层架构的核心。 上一篇我们学习到&#xff0c;Elasticsearch的底层是由Lucene实…

【Spring Boot】# 使用@Scheduled注解无法执行定时任务

1. 前言 在 Spring Boot中&#xff0c;使用Scheduled注解来定义定时任务时&#xff0c;定时任务不执行&#xff1b;或未在规定时间执行。 import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;Component public c…

Ubuntu24.04下的docker问题

按官网提示是可以安装成功的&#xff0c;但是curl无法使用https下载&#xff0c;会造成下述语句执行失败 # Add Dockers official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https…