Redis---LRU原理与算法实现
文章目录
- LRU概念理解
- LRU原理
- 基于HashMap和双向链表实现LRU
- Redis中的LRU的实现
- LRU时钟
- 淘汰策略
- 近似LRU的实现
- LRU算法的优化
- Redis LRU的核心代码逻辑
- Redis LRU的核心代码逻辑
- Redis LRU的配置参数
- Redis LRU的优缺点
- Redis LRU的优缺点
LRU概念理解
LRU(Least Recently Used) 最近最少使用算法,是一种常用的页面置换算法,广泛应用于操作系统中的内存管理和缓存系统。LRU 的基本思想是:当缓存空间满时,当需要置换页面时,选择最近最少使用的页面进行淘汰。
LRU原理
可以用一个特殊的栈来保存当前正在使用的各个页面的页面号。当一个新的进程访问某页面时,便将该页面号压入栈顶,其他的页面号往栈底移,如果内存不够,则将栈底的页面号移除。这样,栈顶始终是最新被访问的页面的编号,而栈底则是最近最久未访问的页面的页面号。
在一般标准的操作系统教材里,会用下面的方式来演示 LRU 原理,假设内存只能容纳3个页大小,按照 7 0 1 2 0 3 0 4 的次序访问页。假设内存按照栈的方式来描述访问时间,在上面的,是最近访问的,在下面的是,最远时间访问的,LRU就是这样工作的。
基于HashMap和双向链表实现LRU
HahsMap用于快速查找到结点所在位置,然后将使用到的结点放在对头,这样最近最少使用的结点自然就落入到队尾。双向链表提供了良好的灵活性,两边可达。如下图所示。
假设我们需要执行如下操作
save(“key1”, 7)
save(“key2”, 0)
save(“key3”, 1)
save(“key4”, 2)
get(“key2”)
save(“key5”, 3)
get(“key2”)
save(“key6”, 4)
使用HashMap + 双向链表数据结构实现的LRU操作双向链表部分的轨迹如下。
核心操作的步骤:
- save(key, value)
- 首先在 HashMap 找到 Key 对应的节点,如果节点存在,更新节点的值,并把这个节点移动队头。
- 如果不存在,需要构造新的节点,并且尝试把节点塞到队头。
- 如果LRU空间不足,则通过 tail 淘汰掉队尾的节点,同时在 HashMap 中移除 Key。
- get(key),通过 HashMap 找到 LRU 链表节点,把节点插入到队头,返回缓存的值。
完整基于Java的代码参考如下
package LRU;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;public class LRUCache {// 定义双向链表的节点类class DLinkedNode {String key; // 节点的键int value; // 节点的值DLinkedNode pre; // 前驱节点DLinkedNode post; // 后继节点}// 使用ConcurrentHashMap来存储缓存数据,保证线程安全private ConcurrentMap<String, DLinkedNode> cache = new ConcurrentHashMap<String, DLinkedNode>();private int count; // 当前缓存中的元素数量private int capacity; // 缓存的最大容量private DLinkedNode head, tail; // 双向链表的头节点和尾节点// 构造函数,初始化LRU缓存public LRUCache(int capacity) {this.count = 0;this.capacity = capacity;// 初始化头节点和尾节点head = new DLinkedNode();head.pre = null;tail = new DLinkedNode();tail.post = null;// 头节点和尾节点相互连接head.post = tail;tail.pre = head;}// 获取缓存中的值public int get(String key) {DLinkedNode node = cache.get(key);if(node == null){return -1; // 如果缓存中没有该键,返回-1}// 将该节点移动到链表头部,表示最近使用moveToHead(node);return node.value;}// 向缓存中插入或更新值public void put(String key, int value) {DLinkedNode node = cache.get(key);if (node != null) {// 如果键已存在,更新值并将节点移动到链表头部node.value = value;moveToHead(node);return;}// 创建新节点DLinkedNode newNode = new DLinkedNode();newNode.key = key;newNode.value = value;// 将新节点加入缓存和链表头部cache.put(key, newNode);addNode(newNode);++count;// 如果缓存已满,移除链表尾部的节点(最久未使用的节点)if(count > capacity){DLinkedNode tail = popTail();cache.remove(tail.key);--count;}}// 将节点添加到链表头部private void addNode(DLinkedNode node){node.pre = head;node.post = head.post;head.post.pre = node;head.post = node;}// 从链表中移除节点private void removeNode(DLinkedNode node){DLinkedNode pre = node.pre;DLinkedNode post = node.post;pre.post = post;post.pre = pre;}// 将节点移动到链表头部private void moveToHead(DLinkedNode node){removeNode(node);addNode(node);}// 移除链表尾部的节点并返回该节点private DLinkedNode popTail(){DLinkedNode res = tail.pre;removeNode(res);return res;}
}
测试LRUCache
package LRU;public class LRUCacheTest {public static void main(String[] args) {// 创建一个容量为 3 的 LRU 缓存LRUCache cache = new LRUCache(3);// 插入键值对cache.put("key1", 1);cache.put("key2", 2);cache.put("key3", 3);// 测试获取操作System.out.println("key1 的值: " + cache.get("key1")); // 应返回 1System.out.println("key2 的值: " + cache.get("key2")); // 应返回 2System.out.println("key3 的值: " + cache.get("key3")); // 应返回 3// 插入新键值对,触发淘汰策略(key1 是最久未使用的,应被淘汰)cache.put("key4", 4);// 测试淘汰策略System.out.println("key1 的值: " + cache.get("key1")); // 应返回 -1,因为 key1 已被淘汰System.out.println("key4 的值: " + cache.get("key4")); // 应返回 4// 更新现有键的值,并测试是否移动到链表头部cache.put("key2", 20);System.out.println("key2 更新后的值: " + cache.get("key2")); // 应返回 20// 插入新键值对,触发淘汰策略(key3 是最久未使用的,应被淘汰)cache.put("key5", 5);// 测试淘汰策略System.out.println("key3 的值: " + cache.get("key3")); // 应返回 -1,因为 key3 已被淘汰System.out.println("key5 的值: " + cache.get("key5")); // 应返回 5// 打印当前缓存中的所有键值对System.out.println("当前缓存内容:");System.out.println("key2: " + cache.get("key2")); // 应返回 20System.out.println("key4: " + cache.get("key4")); // 应返回 4System.out.println("key5: " + cache.get("key5")); // 应返回 5}
}
输出结果:
Redis中的LRU的实现
Redis 的 LRU 实现与传统的 LRU 算法有所不同。由于 Redis 是一个高性能的内存数据库,完全实现标准的 LRU 算法会带来较大的性能开销。因此,Redis 采用了一种 近似 LRU(Approximated LRU) 算法,在保证性能的同时,尽可能接近标准的 LRU 行为。
LRU时钟
Redis 为每个对象(键值对)维护一个 lru
字段,用于记录该对象最后一次被访问的时间戳。这个时间戳是一个 24 位的整数,表示从 Redis 启动开始计算的秒数的低 24 位。
- LRU 时钟的更新:
- 每次访问一个键时(例如
GET
或SET
),Redis 会更新该键的lru
字段为当前的 LRU 时钟值。 - LRU 时钟的值会定期更新(默认每 100 毫秒更新一次)。
- 每次访问一个键时(例如
淘汰策略
当 Redis 需要淘汰键时(例如内存不足时),会根据配置的淘汰策略选择一个键进行删除。Redis 支持多种淘汰策略,其中与 LRU 相关的策略包括:
volatile-lru
:从设置了过期时间的键中,淘汰最近最少使用的键。allkeys-lru
:从所有键中,淘汰最近最少使用的键。
近似LRU的实现
Redis 并不完全遍历所有键来找到最久未使用的键,而是通过以下方式近似实现 LRU:
- 随机采样:Redis 会随机选择一定数量的键(默认是 5 个),然后从这些键中淘汰
lru
值最小的键(即最久未使用的键)。 - 采样数量:可以通过配置
maxmemory-samples
参数来调整采样数量。采样数量越多,淘汰的精度越高,但性能开销也越大。
LRU算法的优化
为了进一步优化性能,Redis 做了一些额外的优化:
- 惰性删除:Redis 不会在每次访问时都更新
lru
字段,而是通过一些启发式方法减少更新频率。 - 淘汰池:Redis 维护一个淘汰池(eviction pool),用于缓存一些候选键,避免每次淘汰时都需要重新采样。
Redis LRU的核心代码逻辑
以下是 Redis 中 LRU 实现的核心逻辑(伪代码):
// 更新键的 LRU 时间戳
void updateLRU(redisObject *obj) {obj->lru = getCurrentLRUClock();
}// 获取当前的 LRU 时钟
unsigned int getCurrentLRUClock() {return (server.unixtime & LRU_CLOCK_MAX) | (server.lruclock & ~LRU_CLOCK_MAX);
}// 近似 LRU 淘汰算法
void evictKeysUsingLRU() {int sample_count = server.maxmemory_samples;redisObject *best_key = NULL;unsigned int best_lru = LRU_CLOCK_MAX;// 随机采样for (int i = 0; i < sample_count; i++) {redisObject *key = getRandomKey();if (key->lru < best_lru) {best_key = key;best_lru = key->lru;}}// 淘汰最久未使用的键if (best_key != NULL) {deleteKey(best_key);}
}
Redis LRU的核心代码逻辑
以下是 Redis 中 LRU 实现的核心逻辑(伪代码):
// 更新键的 LRU 时间戳
void updateLRU(redisObject *obj) {obj->lru = getCurrentLRUClock();
}// 获取当前的 LRU 时钟
unsigned int getCurrentLRUClock() {return (server.unixtime & LRU_CLOCK_MAX) | (server.lruclock & ~LRU_CLOCK_MAX);
}// 近似 LRU 淘汰算法
void evictKeysUsingLRU() {int sample_count = server.maxmemory_samples;redisObject *best_key = NULL;unsigned int best_lru = LRU_CLOCK_MAX;// 随机采样for (int i = 0; i < sample_count; i++) {redisObject *key = getRandomKey();if (key->lru < best_lru) {best_key = key;best_lru = key->lru;}}// 淘汰最久未使用的键if (best_key != NULL) {deleteKey(best_key);}
}
Redis LRU的配置参数
maxmemory
:- 设置 Redis 实例的最大内存限制。
- 当内存使用达到该限制时,Redis 会根据淘汰策略删除键。
maxmemory-policy
:- 设置淘汰策略,支持以下选项:
volatile-lru
:从设置了过期时间的键中淘汰最近最少使用的键。allkeys-lru
:从所有键中淘汰最近最少使用的键。volatile-random
:从设置了过期时间的键中随机淘汰键。allkeys-random
:从所有键中随机淘汰键。volatile-ttl
:从设置了过期时间的键中淘汰剩余生存时间(TTL)最短的键。noeviction
:不淘汰任何键,直接返回错误。
- 设置淘汰策略,支持以下选项:
maxmemory-samples
:- 设置 LRU 淘汰时的采样数量。
- 默认值为 5,表示每次淘汰时随机选择 5 个键,然后淘汰其中最久未使用的键。
- 增加该值可以提高淘汰的精度,但会增加 CPU 开销。
Redis LRU的优缺点
优点:
- 高性能:
- 通过随机采样和近似算法,Redis 的 LRU 实现避免了完全遍历所有键的开销。
- 灵活性:
- 支持多种淘汰策略,可以根据业务需求灵活配置。
- 内存友好:
- 每个键只需要额外存储 24 位的
lru
字段,内存开销较小。
- 每个键只需要额外存储 24 位的
缺点:
- 近似性:
- Redis 的 LRU 是近似的,可能无法完全准确地淘汰最久未使用的键。
- 采样数量影响精度:
- 采样数量较少时,淘汰的精度可能较低。
择 5 个键,然后淘汰其中最久未使用的键。 - 增加该值可以提高淘汰的精度,但会增加 CPU 开销。
- 采样数量较少时,淘汰的精度可能较低。
Redis LRU的优缺点
优点:
- 高性能:
- 通过随机采样和近似算法,Redis 的 LRU 实现避免了完全遍历所有键的开销。
- 灵活性:
- 支持多种淘汰策略,可以根据业务需求灵活配置。
- 内存友好:
- 每个键只需要额外存储 24 位的
lru
字段,内存开销较小。
- 每个键只需要额外存储 24 位的
缺点:
- 近似性:
- Redis 的 LRU 是近似的,可能无法完全准确地淘汰最久未使用的键。
- 采样数量影响精度:
- 采样数量较少时,淘汰的精度可能较低。
相关文章:

Redis---LRU原理与算法实现
文章目录 LRU概念理解LRU原理基于HashMap和双向链表实现LRURedis中的LRU的实现LRU时钟淘汰策略近似LRU的实现LRU算法的优化 Redis LRU的核心代码逻辑Redis LRU的核心代码逻辑Redis LRU的配置参数Redis LRU的优缺点Redis LRU的优缺点 LRU概念理解 LRU(Least Recentl…...

matlab 包围盒中心匹配法实现点云粗配准
目录 一、算法原理1、原理概述2、参考文献二、代码实现三、结果展示1、初始位置2、配准结果本文由CSDN点云侠原创,原文链接,首发于:20255年3月3日。 一、算法原理 1、原理概述 包围盒中心匹配法是将源点云 P P P...

Mermaid语法介绍
一、基础语法 图表声明 使用 graph TD(自上而下)或 graph LR(从左到右)定义图表方向,节点间用箭头连接。例如: #mermaid-svg-WLayaaK0Ui6cKr5Z {font-family:"trebuchet ms",verdana,arial,sans…...
RHCE9.0版本笔记3:创建、查看和编辑文本文件
一、文件操作在RHCE中的核心地位 无论是配置系统服务(如httpd/sshd)、编写Ansible Playbook,还是分析日志文件,都离不开对文本文件的精确控制。 文件创建四大技法 1.快速创建空文件 # 标准创建方式 $ touch server.conf # 批量…...

VSCode知名主题带毒 安装量900万次
目前微软已经从 Visual Studio Marketplace 中删除非常流行的主题扩展 Material Theme Free 和 Material Theme Icons,微软称这些主题扩展包含恶意代码。 统计显示这些扩展程序的安装总次数近 900 万次,在微软实施删除后现在已安装这些扩展的开发者也会…...
deepseek、腾讯元宝deepseek R1、百度deepseekR1关系
分析与结论 区别与联系 技术基础与定制方向: DeepSeek官网R1版本:作为基础版本,通常保留通用性设计,适用于广泛的AI应用场景(如自然语言处理、数据分析等)。其优势在于技术原生性和官方直接支持。腾讯元宝…...

二、QT和驱动模块实现智能家居-----5、通过QT控制LED
在QT界面,我们要实现点击“LED”按钮就可以控制板子上的LED。LED接线图如下: 在Linux 系统里,我们可以使用2种方法去操作上面的LED: ① 使用GPIO SYSFS系统:这需要一定的硬件知识,需要设置引脚的方向、数值…...

基于Android平台的SOME/IP测试模块 EPT-ETS
在汽车产业智能化、网联化的时代浪潮中,汽车电子系统正经历着前所未有的变革。SOME/IP(Scalable service-Oriented MiddlewarE over IP)协议作为汽车电子通信领域的关键技术,其稳定性、可靠性与高效性对于整车性能的提升起着至关重…...

QT实现计算器
1:在注册登录的练习里面, 追加一个QListWidget 项目列表 要求:点击注册之后,将账号显示到 listWidget上面去 以及,在listWidget中双击某个账号的时候,将该账号删除 Widget.h #ifndef WIDGET_H #define…...

Go红队开发—语法补充
文章目录 错误控制使用自定义错误类型错误包装errors.Is 和 errors.Aspanic捕获、recover 、defer错误控制练习 接口结构体实现接口基本类型实现接口切片实现接口 接口练习Embed嵌入文件 之前有师傅问这个系列好像跟红队没啥关系,前几期确实没啥关系,因为…...
二、Redis 安装与基本配置:全平台安装指南 服务器配置详解
Redis 安装与基本配置:全平台安装指南 & 服务器配置详解 Redis 作为高性能的内存数据库,其安装和配置是使用 Redis 的第一步。本篇文章将全面介绍 Redis 的安装方式,覆盖 Windows、Linux、Docker 环境,并详细讲解 Redis 的基础配置,包括 持久化、日志、端口设置等。此…...

halcon学习笔记1
环境的搭建就不说了,主要是作者在入职后的实际学习与实践。 打开应用程序 这里作者的个人理解是1号区域主要是可以观察到读取的图像以及后续对图像进行何种操作,2的算子类似于Opencv中的API,可以在上面进行参数的调整,例如read_I…...
解决Docker拉取镜像超时错误,docker: Error response from daemon:
当使用docker pull或docker run时遇到net/http: request canceled while waiting for connection的报错,说明Docker客户端在访问Docker Hub时出现网络连接问题。可以不用挂加速器也能解决,linux不好用clash。以下是经过验证的方法(感谢轩辕镜…...
Masscan下载Linux安装
masscan 是一款高速的端口扫描工具,能够在极短的时间内扫描大量IP地址和端口。以下是关于如何在Linux系统上下载并安装 masscan 的详细步骤。 ### 通过包管理器安装 对于一些Linux发行版,你可以直接使用系统的包管理器来安装 masscan。例如,…...

js的简单介绍
一.javascript(是什么) 是一种运行在客户端(浏览器)的编程语言,实现人机交互效果 作用 网页特效(监听客户的一些行为让网页做出对应的反馈)表单验证(针对表格数据的合法性进行判断)数据交互(获取后台的数据…...

神经网络 - 激活函数(Swish函数、GELU函数)
一、Swish 函数 Swish 函数是一种较新的激活函数,由 Ramachandran 等人在 2017 年提出,其数学表达式通常为 其中 σ(x) 是 Sigmoid 函数(Logistic 函数)。 如何理解 Swish 函数 自门控特性 Swish 函数可以看作是对输入 x 进行“…...

关于后端使用Boolean或boolean时前端收到的参数的区别
当后端使用的是Boolean时,调用的方法是setIsLoginUser,前端收到的参数的参数名是isLoginUser 而当后端使用的是boolean时,调用的方法是setLoginUser,前端收到的参数的参数名是loginUser 封装类和基本数据类型在使用时需要注意这…...

笔记:代码随想录算法训练营第35天: 01背包问题 二维、 01背包问题 一维 、LeetCode416. 分割等和子集
学习资料:代码随想录 这一块儿学得挺痛苦 注:文中含大模型生成内容 动态规划:01背包理论基础 卡码网第46题 思路:五部曲 定义:dp[i][j]为第i个物品背包容量为j,能装下的最大价值 递推公式࿱…...
安装 Windows Docker Desktop - WSL问题
一、关联文章: 1、Docker Desktop 安装使用教程 2、家庭版 Windows 安装 Docker 没有 Hyper-V 问题 3、打开 Windows Docker Desktop 出现 Docker Engine Stopped 问题 二、问题解析 打开 Docker Desktop 出现问题,如下: Docker Desktop - WSL update failed An error o…...

Spring MVC 返回数据
目录 1、什么是 SpringMVC2、返回数据2.1、返回 JSON 对象2.2、请求转发2.3、请求重定向2.4、自定义返回的内容 1、什么是 SpringMVC 1、Tomcat 和 Servlet 分别是什么?有什么关系? Servlet 是 java 官方定义的 web 开发的标准规范;Tomcat 是…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...