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 一、基础概念 信息交换方式 电路交换 电话交换机接通…

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 遍历对象的值 遍历…

蓝桥杯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…

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

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

STM32总体架构简单介绍

目录 一、引言 二、STM32的总体架构 1、三个被动单元 &#xff08;1&#xff09;内部SRAM &#xff08;2&#xff09;内部闪存存储器 &#xff08;3&#xff09;AHB到APB的桥&#xff08;AHB to APBx&#xff09; 2、四个主动&#xff08;驱动&#xff09;单元 &#x…

C# Postman或者PostApi调试前端webapi接口发送带有request/body/head信息

知识&#xff1a; 前端接口&#xff0c;表单形式提交。 req.ContentType "application/x-www-form-urlencoded"; x-www-form-urlencoded 是一种常见的 MIME 类型&#xff0c;用于将键值对编码为 HTTP 请求体中的 URL 编码格式。在 Web API 中&#xff0c;x-www-for…

李宏毅机器学习课程知识点摘要(1-5集)

前5集 过拟合&#xff1a; 参数太多&#xff0c;导致把数据集刻画的太完整。而一旦测试集和数据集的关联不大&#xff0c;那么预测效果还不如模糊一点的模型 所以找的数据集的量以及准确性也会影响 由于线性函数的拟合一般般&#xff0c;所以用一组函数去分段来拟合 sigmoi…

七、SElinux

一、SElinux简介 SELinux是Security-Enhanced Linux的缩写&#xff0c;意思是安全强化的linuxSELinux 主要由美国国家安全局(NSA)开发&#xff0c;当初开发的目的是为了避免资源的误用传统的访问控制在我们开启权限后&#xff0c;系统进程可以直接访问当我们对权限设置不严谨时…

小程序25- iconfont 字体图标的使用

项目中使用到图标&#xff0c;一般由公司设计进行设计&#xff0c;设计好后上传到阿里巴巴矢量图标库 日常开发过程中&#xff0c;也可以通过 iconfont 图标库下载使用自带的图标 补充&#xff1a;使用 iconfont 图标库报错&#xff1a;Failed to load font 操作步骤&#xff…

鸢尾花植物的结构认识和Python中scikit-learn工具包的安装

鸢尾花植物的结构认识和Python中scikit-learn工具包的安装 鸢尾花植物的结构认识和Python中scikit-learn工具包的安装 鸢尾花植物的结构认识和Python中scikit-learn工具包的安装一、鸢尾花的认识1.1 对花结构和功能认识1.2、鸢尾花认识1.2.1 鸢尾花种类1.2.2 鸢尾花结构 二. Py…

Unity3D 截图

使用 Unity3D 自带的截图接口&#xff0c;制作截图工具。 截图 有时候我们想对 Unity 的窗口进行截图&#xff0c;如果直接使用一些截图工具&#xff0c;很难截取到一张完整分辨率的图片&#xff08;例如&#xff0c;我们想要截取一张 1920 * 1080 的图片&#xff09;。 其实…

Mysql的加锁情况详解

最近在复习mysql的知识点&#xff0c;像索引、优化、主从复制这些很容易就激活了脑海里尘封的知识&#xff0c;但是在mysql锁的这一块真的是忘的一干二净&#xff0c;一点映像都没有&#xff0c;感觉也有点太难理解了&#xff0c;但是还是想把这块给啃下来&#xff0c;于是想通…

丹摩征文活动 | AI创新之路,DAMODEL助你一臂之力GPU

目录 前言—— DAMODEL&#xff08;丹摩智算&#xff09; 算力服务 直观的感受算力提供商的强大​ 平台功能介绍​ 镜像选择 云磁盘创建 总结 前言—— 只需轻点鼠标,开发者便可拥有属于自己的AI计算王国 - 从丰富的GPU实例选择,到高性能的云磁盘,再到预配置的深度学习…

Linux之日志

日志 在编写网络服务器, 各种软件时, 程序一定要打印一些日志信息. 1. 可以向显示器打印, 也可以向文件中写入. 2. 日志是软件在运行时记录的流水账, 用于排查服务进程挂掉的信息. 其中必须要有的是: 日志等级, 时间, 日志内容.可选的是文件名, 代码行数, 进程pid 等 日志…

IDEA指定Maven的settings不生效问题处理

文章目录 一、问题描述二、问题分析三、问题解决 一、问题描述 在Idea中手动指定了maven的settings配置文件&#xff0c;但是一直没生效。 如下图&#xff1a;设置加载settings-aliyun.xml文件&#xff0c;但是最后发现还是在加载settings.xml文件 二、问题分析 ‌在Intel…