golang分布式缓存项目 Day1 LRU 缓存淘汰策略

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

LRU缓存淘汰策略

三种缓存淘汰策略

  1. FIFO(First In, First Out)先进先出
    原理:FIFO是一种最简单的缓存淘汰算法,它基于“先进先出”的原则。当缓存满了之后,最早进入缓存的数据将被首先淘汰。
    适用场景:适用于数据访问模式中,数据的访问顺序与数据进入缓存的顺序一致的场景。
    优点:实现简单,公平性较好,每个数据都有相同的淘汰机会。
    缺点:可能会淘汰掉一些频繁访问的数据,导致缓存命中率降低。
  2. LFU(Least Frequently Used)最少使用
    原理:LFU算法淘汰那些在最近一段时间内访问次数最少的数据。它的核心思想是,如果数据在过去被访问得少,那么在未来被访问的可能性也低。
    适用场景:适用于那些访问模式中,某些数据被频繁访问,而其他数据则很少被访问的场景。
    优点:可以有效地保留那些频繁访问的数据,提高缓存的命中率。
    缺点:实现相对复杂,需要跟踪每个数据的访问频率,并且在数据访问模式变化时可能需要调整策略。
  3. LRU(Least Recently Used)最近最少使用
    原理:LRU算法淘汰那些最近最少被使用的数据。它基于这样一个假设:如果数据最近被访问过,那么在未来它被访问的可能性也更高。
    适用场景:适用于那些数据访问模式中,最近访问过的数据在未来很可能再次被访问的场景。
    优点:可以有效地利用缓存空间,提高缓存命中率,是实践中最常用的缓存淘汰算法之一。
    缺点:实现相对复杂,需要维护一个数据访问的时间顺序,以便快速找到最近最少使用的数据。

LRU算法实现

算法图解

在这里插入图片描述
这张图很好地表示了 LRU 算法最核心的 2 个数据结构

  • 绿色的是字典(map),存储键和值的映射关系。这样根据某个键(key)查找对应的值(value)的复杂是O(1),在字典中插入一条记录的复杂度也是O(1)。
  • 红色的是双向链表(double linked
    list)实现的队列。将所有的值放到双向链表中,这样,当访问到某个值时,将其移动到队尾的复杂度是O(1),在队尾新增一条记录以及删除一条记录的复杂度均为O(1)。

具体代码实现

建立缓存池结构体

package project

import (
	"container/list"
)

type Cache struct {
	maxBytes  int64
	nbytes    int64
	ll        *list.List
	cache     map[string]*list.Element
	OnEvicted func(key string, value Value)
}

type entry struct {
	key   string
	value Value
}

type Value interface {
	Len() int
}

  • 在这里我们直接使用 Go 语言标准库实现的双向链表list.List。
  • 字典的定义是 map[string]*list.Element,键是字符串,值是双向链表中对应节点的指针。
  • maxBytes 是允许使用的最大内存,nbytes 是当前已使用的内存,OnEvicted 是某条记录被移除时的回调函数,可以为nil。
  • 键值对 entry 是双向链表节点的数据类型,在链表中仍保存每个值对应的 key 的好处在于,淘汰队首节点时,需要用 key从字典中删除对应的映射。
  • 为了通用性,我们允许值是实现了 Value 接口的任意类型,该接口只包含了一个方法 Len() int,用于返回值所占用的内存大小

PS:list.Element是一个结构体其定义为

type Element struct {
	// Next and previous pointers in the doubly-linked list of elements.
	// To simplify the implementation, internally a list l is implemented
	// as a ring, such that &l.root is both the next element of the last
	// list element (l.Back()) and the previous element of the first list
	// element (l.Front()).
	next, prev *Element

	// The list to which this element belongs.
	list *List

	// The value stored with this element.
	Value any
}

所以element.Value的值可以是任何类型的,需要我们自己具体实现的时候定义下来.

方便实例化 Cache,实现 New() 函数:

func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
	return &Cache{
		maxBytes:  maxBytes,
		ll:        list.New(),
		cache:     make(map[string]*list.Element),
		OnEvicted: onEvicted,
	}
}

查找功能

实现查找功能,具体步骤如下:

找到了
没找到
查找缓存池
是否找到
从字典中找到对应的双向链表的节点
将其放到双链表队列的队尾代表最近刚刚使用
返回空

代码实现

func (c *Cache) Get(Key string) (value Value, ok bool) {
	if ele, ok := c.cache[Key]; ok {
		c.ll.MoveToFront(ele)
		kv := ele.Value.(*entry)
		return kv.value, ok
	}
	return
}

删除功能

具体步骤如下:

找到双向链表中队首元素
更新内存池中所用的空间nbytes
删掉链表和map对应的元素
如果回调函数不空则调用

代码实现

func (c *Cache) RemoveOldest() {
	ele := c.ll.Back()
	if ele != nil {
		c.ll.Remove(ele)
		kv := ele.Value.(*entry)
		delete(c.cache, kv.key)
		c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
		if c.OnEvicted != nil {
			c.OnEvicted(kv.key, kv.value)
		}
	}
}

新增/修改功能

具体步骤:

存在
不存在
超出
查找缓存池中的键是否存在
更新对应节点的值并将其移到链表队尾
对应更新所用内存nbytes
判断是否超出最大内存
将其添加到缓存池和链表队尾中
对应更新所用内存nbytes
循环调用删除函数直至小于最大内存

代码实现

func (c *Cache) Add(key string, value Value) {
	if ele, ok := c.cache[key]; ok {
		c.ll.MoveToFront(ele)
		kv := ele.Value.(*entry)
		c.nbytes += int64(value.Len()) - int64(kv.value.Len())
		kv.value = value
	} else {
		ele := c.ll.PushFront(&entry{key, value})
		c.cache[key] = ele
		c.nbytes += int64(len(key)) + int64(value.Len())
	}
	for c.maxBytes != 0 && c.maxBytes < c.nbytes {
		c.RemoveOldest()
	}
}

测试

测试所用样例及其Len()方法实现

package project

import (
	"fmt"
	"testing"
)

type String string

func (d String) Len() int {
	return len(String(d))
}

type test = []struct {
	name       string
	id         int
	key        string
	value      String
	expectedOk bool
}

var tests = test{
	{
		id:         1,
		name:       "test existed key",
		key:        "key1",
		value:      "6666",
		expectedOk: true,
	},
	{
		id:         2,
		name:       "test existed key",
		key:        "key2",
		value:      "1234",
		expectedOk: true,
	},
	{
		id:         3,
		name:       "test existed key",
		key:        "key3",
		value:      "1111",
		expectedOk: true,
	},
	{
		id:         4,
		name:       "test existed key",
		key:        "key4",
		value:      "1314",
		expectedOk: true,
	},
}

测试增加/修改和删除功能

代码实现

func TestGetAndRemove(t *testing.T) {
	lru := New(0, nil)
	for _, tt := range tests {
		lru.Add(tt.key, tt.value)
		if findkey, ok := lru.Get(tt.key); ok == tt.expectedOk && findkey == tt.value {
			fmt.Printf("find key%d successfully\n", tt.id)
		} else {
			fmt.Printf("find key%d failed\n", tt.id)
		}
	}

	lru.RemoveOldest()
	for _, tt := range tests {
		if findkey, ok := lru.Get(tt.key); ok == tt.expectedOk && findkey == tt.value {
			fmt.Printf("find key%d successfully\n", tt.id)
		} else {
			fmt.Printf("find key%d failed\n", tt.id)
		}
	}
}

测试结果
在这里插入图片描述

测试超出最大容量是能否删除最久未使用的节点

代码实现

func TestOverCapacity(t *testing.T) {
	lru := New(int64(24), nil)
	for _, tt := range tests {
		lru.Add(tt.key, tt.value)
		fmt.Printf("add key%d successfully, prsent lru usedcap = %d\n", tt.id, lru.nbytes)
	}

	for _, tt := range tests {
		if findkey, ok := lru.Get(tt.key); ok == tt.expectedOk && findkey == tt.value {
			fmt.Printf("find key%d successfully\n", tt.id)
		} else {
			fmt.Printf("find key%d failed\n", tt.id)
		}
	}
}

测试结果
在这里插入图片描述

测试回调函数

代码实现

func (c *Cache) CallBack(key string, value Value) {
	if _, ok := c.Get(key); ok {
		return
	} else {
		c.Add(key, value)
		c.ll.MoveToFront(c.cache[key])
	}
}

func TestOnEvicted(t *testing.T) {

	LoseKeys := make([]string, 0)
	lru := New(int64(8), func(key string, value Value) {
		LoseKeys = append(LoseKeys, key)
	})

	for _, tt := range tests {
		lru.Add(tt.key, tt.value)
		fmt.Printf("add key%d successfully, prsent lru usedcap = %d\n", tt.id, lru.nbytes)
	}

	for _, tt := range LoseKeys {
		fmt.Printf("%s被丢弃并保存在日志中\n", tt)
	}
}

测试结果
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

面向对象的需求分析和设计(一)

[toc] 1. 引言 前一篇文章《我对需求分析的理解》提到了面向对象分析和设计&#xff0c;正好最近又重新有重点的读了谭云杰著的《Think in UML》&#xff0c;感觉有必要写把书中一些核心内容观点以及自己的想法整理出来&#xff0c;一是方便自己日后的复习&#xff0c;另外也…

php中ajax怎么使用【小白专用24.11.12】

在PHP中&#xff0c;使用Ajax可以实现页面异步加载和动态数据交互。下面是使用Ajax的基本方法&#xff1a; <?php // ajax_endpoint.php// 处理请求&#xff0c;并返回JSON格式的响应 $responseData array(message > Hello from PHP!); header(Content-Type: applicati…

【css】html里面的图片宽度设为百分比,高度要与宽度一样

场景&#xff1a;展示图片列表的时候&#xff0c;原始图片宽高不一致。 外层div的宽度自适应&#xff0c;图片宽度不能固定数值&#xff0c;只能设置百分比。图片高度也不能设置固定数值。 如何让图片的高度与图片的宽度一样呢&#xff1f; html代码 &#xff1a; <div cl…

开源项目推荐——OpenDroneMap无人机影像数据处理

实景三维作为GIS最火的课题&#xff0c;最近在想做一套自己的三维构建工具&#xff0c;考察了几个开源项目&#xff0c;把自己的搜索过程用csdn记录下来&#xff0c;希望也能帮助到各位同仁。 OpenDroneMap&#xff08;ODM&#xff09;是一个开源项目&#xff0c;旨在处理无人…

快速提升ROI,收藏这份Facebook广告投放技巧!

Facebook广告在海外数字营销中占据重要地位。据统计&#xff0c;约有 700 万广告商活跃在该平台上&#xff0c;购买力不容小觑。 然而&#xff0c;当前 Facebook 广告竞争激烈&#xff0c;导致广告位供不应求&#xff0c;成本上升&#xff0c;尤其是在下半年营销旺季中&#xf…

C++提高编程-泛型编程

一、模板&#xff1a; 1.1.模板的概念: 1.模板就是建立通用的模具&#xff0c;大大提高复用性2.例如生活中的模板: 一寸照片模板&#xff1a; PPT模板&#xff1a; 模板的特点&#xff1a; 模板不可以直接使用&#xff0c;它只是一个框架模板的通用并不是万能的 二、泛型编…

漫谈分布式唯一ID

文章目录 本系列前言UUIDDB自增主键Redis incr命令号段模式雪花算法 本系列 漫谈分布式唯一ID&#xff08;本文&#xff09;分布式唯一ID生成&#xff08;二&#xff09;&#xff1a;leaf分布式唯一ID生成&#xff08;三&#xff09;&#xff1a;uid-generator分布式唯一ID生成…

大语言模型LLMs在医学领域的最新进展总结

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 相比其他学科&#xff0c;医学AI&#xff0c;是发表学术成果最多的领域。 医学数据的多样性和复杂性&#xff08;包括文本、图像、基因组数据等&#xff09;&#xff0c;使得…

Vue 学习随笔系列十四 -- JavaScript巧妙用法

JavaScript巧妙用法 文章目录 JavaScript巧妙用法1、String.padStart 函数2、String.padEnd 函数3、tirm 函数3. Object.freeze 函数4. Object.fromEntries 函数5. Object.entries 函数6. Array.prototype.flat 函数 1、String.padStart 函数 在字符串前面进行填充 let temp …

【PGCCC】Postgresql 物理流复制

postgresql 提供了主从复制功能&#xff0c;有基于文件的拷贝和基于 tcp 流的数据传输两种方式。两种方式都是传输 wal 数据&#xff0c;前者是等待生成一个完整的wal文件后&#xff0c;才会触发传输&#xff0c;后者是实时传输的。可以看出来基于文件方式的延迟会比较高&#…

每日小练:Day2

1.乒乓球筐 题目链接&#xff1a;乒乓球筐__牛客网 题目描述&#xff1a; 这道题主要考察B盒是不是A盒的子集&#xff0c;我们可以通过哈希表来做 单哈希表 import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public stat…

esp32学习:如何解决OV5640摄像头发热问题

我们在使用esp开发板过程中&#xff0c;连接ov2640摄像头时&#xff0c;非常正常&#xff0c;但连接ov5640摄像头时&#xff0c;会发现摄像头发烫&#xff0c;非常热&#xff0c;我们网上找解决方案&#xff0c;基本都是加散热片&#xff0c;没有根本解决问题。 前段时间&#…

JQuery封装的ajax

1. 注意&#xff1a; 首先要导jq的包json对象可以用 . 来调用keyjava只能给前端传页面&#xff0c;或者打印的内容String jsonstr json.toJSONString(resultJSON); //将对象转为JSON对象 Json格式和参数解释&#xff1a; <script src"js/jquery-1.10.2.min.js&quo…

【计算机网络】章节 知识点总结

一、计算机网络概述 1. 计算机网络向用户提供的两个最重要的功能&#xff1a;连通性、共享 2. 因特网发展的三个阶段&#xff1a; 第一阶段&#xff1a;从单个网络 ARPANET 向互联网发展的过程。1983 年 TCP/IP 协议成为 ARPANET 上的标准协议。第二阶段&#xff1a;建成三级…

Python+robotframework接口自动化测试实操(超详细总结)

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 目前我们需要考虑的是如何实现关键字驱动实现接口自动化输出&#xff0c;通过关键字的封装实现一定意义上的脚本与用例的脱离&#xff01; robot framework 的…

如何管理好自己的LabVIEW项目

在LabVIEW项目开发中&#xff0c;项目管理对于提高开发效率、确保项目质量、减少错误和维护成本至关重要。以下从项目规划、代码管理、测试与调试、版本控制、团队协作等方面&#xff0c;分享LabVIEW项目管理的体会。 ​ 1. 项目规划与需求分析 关键步骤&#xff1a; 需求分析…

【快速解决】kafka崩了,重启之后,想继续消费,怎么做?

目录 一、怎么寻找我们关心的主题在崩溃之前消费到了哪里&#xff1f; 1、一个问题&#xff1a; 2、查看消费者消费主题__consumer_offsets 3、一个重要前提&#xff1a;消费时要提交offset 二、指定 Offset 消费 假如遇到kafka崩了&#xff0c;你重启kafka之后&#xff0…

matlab建模入门指导

本文以水池中鸡蛋温度随时间的变化为切入点&#xff0c;对其进行数学建模并进行MATLAB求解&#xff0c;以更为通俗地进行数学建模问题入门指导。 一、问题简述 一个煮熟的鸡蛋有98摄氏度&#xff0c;将它放在18摄氏度的水池中&#xff0c;五分钟后鸡蛋的温度为38摄氏度&#x…

51单片机应用开发(进阶)---定时器应用(电子时钟)

实现目标 1、巩固定时器的配置流程&#xff1b; 2、掌握按键、数码管与定时器配合使用&#xff1b; 3、功能1&#xff1a;&#xff08;1&#xff09;简单显示时间。显示格式&#xff1a;88-88-88&#xff08;时-分-秒&#xff09; 4、功能2&#xff1a;&#xff08;1&#…

【外包】软件行业的原始形态,项目外包与独立开发者

【外包】互联网软件行业的原始形态&#xff0c;项目外包与独立开发者 本科期间写的一些东西&#xff0c;最近整理东西看到了&#xff0c;大致整理一下放出来&#xff0c;部分内容来自其他文章&#xff0c;均已引用。 文章目录 1、互联网软件行业的原始形态2、项目订单&#xff…