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

golang分布式缓存项目 Day2 单机并发缓存

:该项目原作者:https://geektutu.com/post/geecache-day1.html。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习。

支持并发读写

接下来我们使用 sync.Mutex 封装 LRU 的几个方法,使之支持并发的读写。在这之前,我们抽象了一个只读数据结构 ByteView 用来表示缓存值,是 GeeCache 主要的数据结构之一。

package project// A ByteView holds an immutable view of bytes.
type ByteView struct {b []byte
}// Len returns the view's length
func (v ByteView) Len() int {return len(v.b)
}// ByteSlice returns a copy of the data as a byte slice.
func (v ByteView) ByteSlice() []byte {return cloneBytes(v.b)
}// String returns the data as a string, making a copy if necessary.
func (v ByteView) String() string {return string(v.b)
}func cloneBytes(b []byte) []byte {c := make([]byte, len(b))copy(c, b)return c
}
  • ByteView 只有一个数据成员,b []byte,b 将会存储真实的缓存值。选择 byte类型是为了能够支持任意的数据类型的存储,例如字符串、图片等。

  • 实现 Len() int 方法,我们在 lru.Cache的实现中,要求被缓存对象必须实现 Value 接口,即 Len() int
    方法,返回其所占的内存大小。

  • b 是只读的,使用 ByteSlice() 方法返回一个拷贝,防止缓存值被外部程序修改。

接下来就可以为 lru.Cache 添加并发特性了。

package projectimport ("goLang/project/lru""sync"
)type cache struct {mu         sync.Mutexlru        *lru.CachecacheBytes int64
}func (c *cache) add(key string, value ByteView) {c.mu.Lock()defer c.mu.Unlock()if c.lru == nil {c.lru = lru.New(c.cacheBytes, nil)}c.lru.Add(key, value)
}func (c *cache) get(key string) (value ByteView, ok bool) {c.mu.Lock()defer c.mu.Unlock()if c.lru == nil {return}if v, ok := c.lru.Get(key); ok {return v.(ByteView), ok}return
}···
  • cache.go 的实现非常简单,实例化 lru,封装 get 和 add 方法,并添加互斥锁 mu。
  • 在 add 方法中,判断了 c.lru 是否为 nil,如果等于 nil 再创建实例。这种方法称之为延迟初始化(Lazy Initialization),一个对象的延迟初始化意味着该对象的创建将会延迟至第一次使用该对象时。主要用于提高性能,并减少程序内存要求。

主体结构Group

Group 是 GeeCache 最核心的数据结构,负责与用户的交互,并且控制缓存值存储和获取的流程。
在这里插入图片描述
我们将在 geecache.go 中实现主体结构 Group,那么 GeeCache 的代码结构的雏形已经形成了。
在这里插入图片描述

回调Getter

我们思考一下,如果缓存不存在,应从数据源(文件,数据库等)获取数据并添加到缓存中。GeeCache 是否应该支持多种数据源的配置呢?不应该,一是数据源的种类太多,没办法一一实现;二是扩展性不好。如何从源头获取数据,应该是用户决定的事情,我们就把这件事交给用户好了。因此,我们设计了一个回调函数(callback),在缓存不存在时,调用这个函数,得到源数据。

// A Getter loads data for a key.
type Getter interface {Get(key string) ([]byte, error)
}// A GetterFunc implements Getter with a function.
type GetterFunc func(key string) ([]byte, error)// Get implements Getter interface function
func (f GetterFunc) Get(key string) ([]byte, error) {return f(key)
}
  • 定义接口 Getter 和 回调函数 Get(key string)([]byte, error),参数是 key,返回值是[]byte。
  • 定义函数类型 GetterFunc,并实现 Getter 接口的 Get 方法。
  • 函数类型实现某一个接口,称之为接口型函数,方便使用者在调用时既能够传入函数作为参数,也能够传入实现了该接口的结构体作为参数。

我们可以写一个测试用例来保证回调函数能够正常工作。

func TestGetter(t *testing.T) {var f Getter = GetterFunc(func(key string) ([]byte, error) {return []byte(key), nil})expect := []byte("key")if v, _ := f.Get("key"); !reflect.DeepEqual(v, expect) {t.Errorf("callback failed")}
}

Group 的定义

接下来是最核心数据结构 Group 的定义:

// A Group is a cache namespace and associated data loaded spread over
type Group struct {name      stringgetter    GettermainCache cache
}var (mu     sync.RWMutexgroups = make(map[string]*Group)
)// NewGroup create a new instance of Group
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {if getter == nil {panic("nil Getter")}mu.Lock()defer mu.Unlock()g := &Group{name:      name,getter:    getter,mainCache: cache{cacheBytes: cacheBytes},}groups[name] = greturn g
}// GetGroup returns the named group previously created with NewGroup, or
// nil if there's no such group.
func GetGroup(name string) *Group {mu.RLock()g := groups[name]mu.RUnlock()return g
}
  • 一个 Group 可以认为是一个缓存的命名空间,每个 Group 拥有一个唯一的名称 name。比如可以创建三个
    Group,缓存学生的成绩命名为 scores,缓存学生信息的命名为 info,缓存学生课程的命名为 courses。
  • 第二个属性是 getter Getter,即缓存未命中时获取源数据的回调(callback)。
  • 第三个属性是 mainCache cache,即一开始实现的并发缓存。 构建函数 NewGroup 用来实例化 Group,并且将group 存储在全局变量 groups 中。
  • GetGroup 用来特定名称的 Group,这里使用了只读锁 RLock(),因为不涉及任何冲突变量的写操作。

Group 的 Get 方法

接下来是 GeeCache 最为核心的方法 Get:

// Get value for a key from cache
func (g *Group) Get(key string) (ByteView, error) {if key == "" {return ByteView{}, fmt.Errorf("key is required")}if v, ok := g.mainCache.get(key); ok {log.Println("[GeeCache] hit")return v, nil} else {log.Println("[cache] unhit")return 	g.load(key)}
}func (g *Group) load(key string) (value ByteView, err error) {return g.getLocally(key)
}func (g *Group) getLocally(key string) (ByteView, error) {bytes, err := g.getter.Get(key)if err != nil {return ByteView{}, err}value := ByteView{b: cloneBytes(bytes)}g.populateCache(key, value)return value, nil
}func (g *Group) populateCache(key string, value ByteView) {g.mainCache.add(key, value)
}
  • Get 方法实现了上述所说的流程 ⑴ 和 ⑶。
  • 流程 ⑴ :从 mainCache 中查找缓存,如果存在则返回缓存值。
  • 流程 ⑶ :缓存不存在,则调用 load 方法,load 调用 getLocally(分布式场景下会调用 getFromPeer 从其他节点获取),getLocally 调用用户回调函数 g.getter.Get() 获取源数据,并且将源数据添加到缓存 mainCache 中(通过 populateCache 方法)

至此,这一章节的单机并发缓存就已经完成了。

PS
为什么load方法定义为调用getlocally方法,不能直接调用getlocally方法吗?
:分布式场景下,load 会先从远程节点获取 getFromPeer失败了再回退到 getLocally,设计时预留了。(为后面的分布式场景下设计预留空间)

测试

首先,用一个 map 模拟耗时的数据库。

var db = map[string]string{"Tom":  "630","Jack": "589","Sam":  "567",
}

创建 group 实例,并测试 Get 方法

func TestGet(t *testing.T) {loadCounts := make(map[string]int, len(db))gee := NewGroup("scores", 2<<10, GetterFunc(func(key string) ([]byte, error) {log.Println("[slow db] search key", key)if v, ok := db[key]; ok {if _, ok := loadCounts[key]; !ok {loadCounts[key] = 0}loadCounts[key] += 1return []byte(v), nil}return nil, fmt.Errorf("%s not exist", key)}))for k, v := range db {if view, err := gee.Get(k); err != nil || view.String() != v {t.Fatal("failed to get value of Tom")} // load from callback functionif _, err := gee.Get(k); err != nil || loadCounts[k] > 1 {t.Fatalf("cache %s miss", k)} // cache hit}if view, err := gee.Get("unknown"); err == nil {t.Fatalf("the value of unknow should be empty, but %s got", view)}
}
  • 在这个测试用例中,我们主要测试了 2 种情况
  • 1)在缓存为空的情况下,能够通过回调函数获取到源数据
  • 2)在缓存已经存在的情况下,是否直接从缓存中获取,为了实现这一点,使用 loadCounts 统计某个键调用回调函数的次数,如果次数大于1,则表示调用了多次回调函数,没有缓存。

测试结果如下:
在这里插入图片描述
上述测试的基本逻辑
第一次搜索db中的键–>不存在,打印[cache] unhit,并调用load(key) ->再调用getlocally(key)方法 ->再调用Getter接口的Get方法,也就是GetterFunc函数,打印[slow db] search key…->调用populateCache->调用add方法将键值对加入缓存池中 ->第二次搜索db中的键 ->存在,打印[Cache] hit–>结束一次循环

相关文章:

golang分布式缓存项目 Day2 单机并发缓存

注&#xff1a;该项目原作者&#xff1a;https://geektutu.com/post/geecache-day1.html。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习。 支持并发读写 接下来我们使用 sync.Mutex 封装 LRU 的几个方法&#xff0c;使之支持并发的读写。在这之…...

一个百度、必应搜索引擎图片获取下载的工具包

前言&#xff1a;前段时间需要一大批图片&#xff0c;跑去百度搜图下载&#xff0c;发现特别麻烦&#xff0c;于是用了一天时间写了一个工具库&#xff0c;方便后续使用&#xff0c;这里分享给大家 imagecapture 是一个用 Go 语言编写的库&#xff0c;旨在从百度和必应等搜索引…...

安全见闻(网络安全篇)

笔记仅供学习&#xff0c;切勿触碰法律红线&#xff01; 以下笔记学习来自B站泷羽Sec&#xff1a;https://space.bilibili.com/350329294?spm_id_from333.337.search-card.all.click 如涉及侵权马上删除文章 1.编程语言 C语言&#xff1a;一种通用的、面向过程的编程语言&am…...

手写一些方法

模拟new方法 function Otaku(name,age) {this.name name;this.age age; this.habit Games}Otaku.prototype.strength 60;Otaku.prototype.sayName function () {console.log("I am " this.name);};function myNew(fn, ...args) {const obj Object.create(f…...

仅需三步!用AI工具免费打造10w+抖音爆款烟火秀视频教程

抖音上的烟火秀视频总能唤起人们对节日的温馨回忆&#xff0c;它们不仅视觉效果震撼&#xff0c;还自带流量属性。我自己在刷到这类视频时&#xff0c;也不禁回想起童年放烟花的快乐时光&#xff0c;那种浓厚的年味让人怀念。这些视频通常伴随着合适的音乐&#xff0c;能够迅速…...

基于redis实现API接口访问次数限制

一&#xff0c;概述 日常开发中会有一个常见的需求&#xff0c;需要限制接口在单位时间内的访问次数&#xff0c;比如说某个免费的接口限制单个IP一分钟内只能访问5次。该怎么实现呢&#xff0c;通常大家都会想到用redis&#xff0c;确实通过redis可以实现这个功能&#xff0c…...

[ Linux 命令基础 3 ] Linux 命令详解-文件和目录管理命令

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…...

npm i 的时候报错: npm ERR! Error: EPERM: operation not permitted, rename

文章目录 噩梦解决办法总结 噩梦 最近改漏洞&#xff0c;这个项目删掉了 node_modules文件夹 重新安装依赖&#xff0c;结果安装一半的时候就一直报这个错。 然后查了很多方法&#xff0c;基本都是下面这些&#xff1a; 权限不够&#xff0c;以管理员运行cmd重新安装。清除 n…...

如何迁移剪映源文件

1、打开剪映&#xff0c;打开全局设置 2、查看草稿位置。把要迁移的文件拷贝到这个路径下面。 3、关闭文件&#xff0c;返回上一层界面&#xff0c;可以看到拷贝到目录下的文件。...

Go语言中的`io.Copy`函数:高效的数据复制解决方案

在Go语言中&#xff0c;io.Copy函数是一个强大而高效的工具&#xff0c;用于将数据从一个io.Reader复制到一个io.Writer。这篇文章将深入探讨io.Copy函数的工作原理、使用方法及其在实际应用中的优势。无论您是后端开发人员还是对Go语言感兴趣的程序员&#xff0c;这篇文章都将…...

datastage在升级版本到11.7之后,部分在11.3上正常执行的SP报错SQLSTATE = 22007: 本机错误代码 = -180

在升级版本到11.7之后&#xff0c;部分在11.3上正常执行的SP开始报错&#xff0c;报的SQL错误是时间参数问题&#xff0c;但是一样的SP可以直接call sp执行&#xff0c;也可以手动调用作业执行&#xff0c;只有设置定时调度时作业会报错&#xff0c; CALLXXX.XXX(1,CURRENT TIM…...

docker——项目部署

什么是Docker&#xff1f; Docker是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可抑制的容器中&#xff0c;然后发布到任何流行的Linux机器上&#xff0c;也可以实现虚拟化。容器完全使用沙盒机制&#xff0c;相互之间不会存在任何接口。几…...

设计模式(Unity)——更新中

设计模式 文章目录 设计模式工厂模式创建方法&#xff08;Create Methods&#xff09;简单工厂&#xff08;Simple Factory&#xff09;工厂方法&#xff08;Method Factory&#xff09;抽象工厂&#xff08;Abstract Factroy&#xff09; 策略模式 工厂模式 创建方法&#xf…...

小程序中引入下载到本地的iconfont字体图标加载不出来问题解决

我这个是uniapp项目,字体图标都是一样的,在vue项目中web端、uniapp运行到h5都没问题,但是运行到小程序加载不出来,报错如下: 不让用本地路径,所以我们要转为base64编码,这里给大家提供一个工具,它可以把本地字体文件转为base64:transfonter 进入官网后,第一步: …...

百度富文本禁止编辑

<script type"text/javascript">$(function () {editorcontent new baidu.editor.ui.Editor();editorcontent.render(authentication);//禁用代码editorcontent.ready(function () {editorcontent.setDisabled();});try {editorcontent.sync();} catch (err) …...

C++开发基础之使用librabbitmq库实现RabbitMQ消息队列通信

1. 前言 RabbitMQ是一个流行的开源消息队列系统&#xff0c;支持多种消息协议&#xff0c;广泛用于构建分布式系统和微服务架构。可以在不同应用程序之间实现异步消息传递。在本文中&#xff0c;我们将熟悉如何使用C与RabbitMQ进行消息通信。 2. 准备工作 在 Windows 平台上…...

头歌网络安全(11.12)

头歌禁止复制解决 必须先下篡改猴&#xff01;&#xff01;&#xff01;&#xff01; 头歌复制助手 Educoder Copy Helperhttps://scriptcat.org/zh-CN/script-show-page/1860 Java生成验证码 第1关&#xff1a;使用Servlet生成验证码 任务描述 本关任务&#xff1a;使用se…...

洛谷 P1725 琪露诺(线段树优化dp)

题目链接 https://www.luogu.com.cn/problem/P1725 思路 我们令 d p [ i ] dp[i] dp[i]表示琪露诺移动到第 i i i个格子时能够获得的最大冰冻指数。 显然&#xff0c;状态转移方程为&#xff1a; d p [ i ] m a x ( d p [ i ] , d p [ k ] a [ i ] ) dp[i] max(dp[i],dp…...

【LeetCode】【算法】19. 删除链表的倒数第N个结点

LeetCode 19. 删除链表的倒数第N个结点 题目描述 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 思路 思路&#xff1a;快慢指针&#xff0c;快指针先移动n步&#xff0c;快慢指针再同时移动直到快指针到达链表末尾&#xff0c;此…...

Python爬虫 | 爬取豆瓣电影Top250的数据

简单记录一下&#xff0c;实现爬取豆瓣电影Top 250的数据。 这里我使用requests库来发送HTTP请求&#xff0c;以及BeautifulSoup库来解析HTML页面。 1.安装requests和BeautifulSoup库。 如果没有安装&#xff0c;可以通过以下命令安装&#xff1a; pip install requests bea…...

mac 中python 安装mysqlclient 出现 ld: library ‘ssl‘ not found错误

1. 出现报错 2. 获取openssl位置 brew info openssl 3. 配置环境变量&#xff08;我的是在~/.bash.profile&#xff09; export LDFLAGS"-L/opt/homebrew/Cellar/openssl3/3.4.0/lib" export CPPFLAGS"-I/opt/homebrew/Cellar/openssl3/…...

完全清除:苹果手机照片怎么彻底删除

在使用iPhone的过程中&#xff0c;由于拍摄积累的照片往往会占用大量存储空间。有时候&#xff0c;我们需要彻底删除这些照片以释放空间或保护隐私。苹果手机照片怎么彻底删除&#xff1f;在此&#xff0c;本文将与你分享一些实用的技巧。 彻底删除的重要性 彻底删除照片不仅涉…...

高德地图多个图片组成标点(自定义点标记内容)

图标的实现自定义点标记内容...

02-1_MVCC版本链清理

MVCC-版本链清理 文章目录 MVCC-版本链清理简介依赖机制Purge 操作的触发时机版本链清理的详细过程示例操作流程延迟清理配置和监控总结 简介 MySQL 中的 MVCC 机制通过版本链来管理数据的多版本存储&#xff0c;以支持高并发的读写操作。然而&#xff0c;随着事务的进行&…...

探索Python视频处理的瑞士军刀:ffmpeg-python库

文章目录 **探索Python视频处理的瑞士军刀&#xff1a;ffmpeg-python库**第一部分&#xff1a;背景介绍第二部分&#xff1a;ffmpeg-python库是什么&#xff1f;第三部分&#xff1a;如何安装ffmpeg-python库&#xff1f;第四部分&#xff1a;简单库函数使用方法1. 视频转码2. …...

进程间通信 - 通道

进程间通信 - 通道 什么是管道&#xff1f; 进程间的通信方式有五种&#xff0c;分别为:管道、信号量、共享内存、消息队列和套接字。 管道:本质上就是一个文件&#xff0c;前面的进程以写方式打开文件&#xff0c;后面的进程以读方式打开。这样前面写完后面读&#xff0c;于…...

华为数通HCIA系列第5次考试-【2024-46周-周一】

文章目录 1、子网掩码有什么作用&#xff0c;和IP地址是什么关系&#xff0c;利用子网掩码可以获取哪些信息&#xff1f;2、已知一个IP地址是192.168.1.1&#xff0c;子网掩码是255.255.255.0&#xff0c;求其网络地址3、已知某主机的IP地址是192.168.100.200&#xff0c;子网掩…...

【Linux】如何通过终端命令查看当前可用网络 WIFI + 设置已配置网络的连接优先级 + 连接/断连网络

【Linux】通过命令行&#xff0c;查看当前可用网络 WIFI 设置已配置网络的连接优先级 连接网络 列出所有可连接网络 nmcli device wifi list这个命令会列出所有可连接 wifi&#xff0c;*表示当前连接。 IN-USE BSSID SSID MODE CHAN …...

华为路由策略配置

一、AS_Path过滤 要求&#xff1a; AR1与AR2、AR2与AR3之间建立EBGP连接 AS10的设备和AS30的设备无法相互通信 1.启动设备 2.配置IP地址 3.配置路由器的EBGP对等体连接&#xff0c;引入直连路由 [AR1]bgp 10 [AR1-bgp]router-id 1.1.1.1 [AR1-bgp]peer 200.1.2.2 as-nu…...

Debezium日常分享系列之:异步 Debezium 嵌入式引擎

Debezium日常分享系列之&#xff1a;异步 Debezium 嵌入式引擎 动机目标非目标保留Kafka Connect模型计划的更改线程池并行运行源任务存储偏移量并发处理CDC事件禁用CDC事件的完全排序自定义记录处理器并行处理记录的选项存储偏移量引擎状态和生命周期防止资源泄漏异常处理退出…...

怎么上传网站源码/临沂seo

数值比较器,数值比较器的作用和原理是什么?一、数值比较器的定义及功能在数字系统中&#xff0c;特别是在计算机中都具有运算功能&#xff0c;一种简单的运算就是比较两个数&#xff21;和&#xff22;的大小。数值比较器就是对两数&#xff21;、&#xff22;进行比较&#x…...

自己架设网站服务器/怎么做推广和宣传平台

引语&#xff1a;人多是好事&#xff01;人多好赚钱。不过这对于技术人员来说&#xff0c;却也不是一个小问题&#xff0c;我对这种问题一直是抱以一颗敬畏之心的。这更多的是一个架构问题&#xff0c;作为一个开发我也就这点见识了&#xff01;看着微信、支付宝等等大公司发着…...

广东省建设工程协会网站/sem和seo是什么意思

Vaadin 支持自定义组件&#xff0c;典型的用法是将各种Vaadin内置的组件组合而成构成自定义组件。 创建自定义组件可以通过派生CustomComponent 然后调用setCompositionRoot 为自定义组件设置根容器。例如&#xff1a; class MyComposite extends CustomComponent {public MyC…...

js制作网页游戏/优化搜索引擎的方法

一、失败的经历 印象中&#xff0c;谷歌建议Ubuntu建议版本为10.4。但是我找不到出处了&#xff0c;模糊的印象。不知道是不是这样&#xff1f; 1.Ubuntu10.4.3-desktop-amd64 Virtual Box 安装git-core失败&#xff0c;后续都不能进行&#xff0c;遂放弃。 2.Ubuntu10.4.4 -d…...

网站建设的背景音乐/留号码的广告网站

更多试题见: http://www.cnblogs.com/zhangzujin/p/6791306.html 参考解答见: http://www.cnblogs.com/zhangzujin/p/3527416.html...

怎样建设网站官网/外链工厂 外链

客户端先与NameNode通信&#xff0c;获取block位置信息&#xff0c;之后线性地先取第一个块&#xff0c;然后接二连三地获取&#xff0c;取回一个块时会进行MD5验证&#xff0c;验证通过后会使read顺利进行完&#xff0c;当最终读完所有的block块之后&#xff0c;拼起来就是一个…...