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

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就是这样工作的。

img

基于HashMap和双向链表实现LRU

HahsMap用于快速查找到结点所在位置,然后将使用到的结点放在对头,这样最近最少使用的结点自然就落入到队尾。双向链表提供了良好的灵活性,两边可达。如下图所示。

img

假设我们需要执行如下操作

save(“key1”, 7)

save(“key2”, 0)

save(“key3”, 1)

save(“key4”, 2)

get(“key2”)

save(“key5”, 3)

get(“key2”)

save(“key6”, 4)

使用HashMap + 双向链表数据结构实现的LRU操作双向链表部分的轨迹如下。

img

核心操作的步骤:

  • 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}
}

输出结果:

image-20250228090835350

Redis中的LRU的实现

Redis 的 LRU 实现与传统的 LRU 算法有所不同。由于 Redis 是一个高性能的内存数据库,完全实现标准的 LRU 算法会带来较大的性能开销。因此,Redis 采用了一种 近似 LRU(Approximated LRU) 算法,在保证性能的同时,尽可能接近标准的 LRU 行为。

LRU时钟

Redis 为每个对象(键值对)维护一个 lru 字段,用于记录该对象最后一次被访问的时间戳。这个时间戳是一个 24 位的整数,表示从 Redis 启动开始计算的秒数的低 24 位。

  • LRU 时钟的更新
    • 每次访问一个键时(例如 GETSET),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的配置参数

  1. maxmemory
    • 设置 Redis 实例的最大内存限制。
    • 当内存使用达到该限制时,Redis 会根据淘汰策略删除键。
  2. maxmemory-policy
    • 设置淘汰策略,支持以下选项:
      • volatile-lru:从设置了过期时间的键中淘汰最近最少使用的键。
      • allkeys-lru:从所有键中淘汰最近最少使用的键。
      • volatile-random:从设置了过期时间的键中随机淘汰键。
      • allkeys-random:从所有键中随机淘汰键。
      • volatile-ttl:从设置了过期时间的键中淘汰剩余生存时间(TTL)最短的键。
      • noeviction:不淘汰任何键,直接返回错误。
  3. maxmemory-samples
    • 设置 LRU 淘汰时的采样数量。
    • 默认值为 5,表示每次淘汰时随机选择 5 个键,然后淘汰其中最久未使用的键。
    • 增加该值可以提高淘汰的精度,但会增加 CPU 开销。

Redis LRU的优缺点

优点:

  1. 高性能
    • 通过随机采样和近似算法,Redis 的 LRU 实现避免了完全遍历所有键的开销。
  2. 灵活性
    • 支持多种淘汰策略,可以根据业务需求灵活配置。
  3. 内存友好
    • 每个键只需要额外存储 24 位的 lru 字段,内存开销较小。

缺点:

  1. 近似性
    • Redis 的 LRU 是近似的,可能无法完全准确地淘汰最久未使用的键。
  2. 采样数量影响精度
    • 采样数量较少时,淘汰的精度可能较低。
      择 5 个键,然后淘汰其中最久未使用的键。
    • 增加该值可以提高淘汰的精度,但会增加 CPU 开销。

Redis LRU的优缺点

优点:

  1. 高性能
    • 通过随机采样和近似算法,Redis 的 LRU 实现避免了完全遍历所有键的开销。
  2. 灵活性
    • 支持多种淘汰策略,可以根据业务需求灵活配置。
  3. 内存友好
    • 每个键只需要额外存储 24 位的 lru 字段,内存开销较小。

缺点:

  1. 近似性
    • Redis 的 LRU 是近似的,可能无法完全准确地淘汰最久未使用的键。
  2. 采样数量影响精度
    • 采样数量较少时,淘汰的精度可能较低。

相关文章:

Redis---LRU原理与算法实现

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

matlab 包围盒中心匹配法实现点云粗配准

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

Mermaid语法介绍

一、基础语法 图表声明 使用 graph TD&#xff08;自上而下&#xff09;或 graph LR&#xff08;从左到右&#xff09;定义图表方向&#xff0c;节点间用箭头连接。例如&#xff1a; #mermaid-svg-WLayaaK0Ui6cKr5Z {font-family:"trebuchet ms",verdana,arial,sans…...

RHCE9.0版本笔记3:创建、查看和编辑文本文件

一、文件操作在RHCE中的核心地位 无论是配置系统服务&#xff08;如httpd/sshd&#xff09;、编写Ansible Playbook&#xff0c;还是分析日志文件&#xff0c;都离不开对文本文件的精确控制。 文件创建四大技法 1.快速创建空文件 # 标准创建方式 $ touch server.conf # 批量…...

VSCode知名主题带毒 安装量900万次

目前微软已经从 Visual Studio Marketplace 中删除非常流行的主题扩展 Material Theme Free 和 Material Theme Icons&#xff0c;微软称这些主题扩展包含恶意代码。 统计显示这些扩展程序的安装总次数近 900 万次&#xff0c;在微软实施删除后现在已安装这些扩展的开发者也会…...

deepseek、腾讯元宝deepseek R1、百度deepseekR1关系

分析与结论 区别与联系 技术基础与定制方向&#xff1a; DeepSeek官网R1版本&#xff1a;作为基础版本&#xff0c;通常保留通用性设计&#xff0c;适用于广泛的AI应用场景&#xff08;如自然语言处理、数据分析等&#xff09;。其优势在于技术原生性和官方直接支持。腾讯元宝…...

二、QT和驱动模块实现智能家居-----5、通过QT控制LED

在QT界面&#xff0c;我们要实现点击“LED”按钮就可以控制板子上的LED。LED接线图如下&#xff1a; 在Linux 系统里&#xff0c;我们可以使用2种方法去操作上面的LED&#xff1a; ① 使用GPIO SYSFS系统&#xff1a;这需要一定的硬件知识&#xff0c;需要设置引脚的方向、数值…...

基于Android平台的SOME/IP测试模块 EPT-ETS

在汽车产业智能化、网联化的时代浪潮中&#xff0c;汽车电子系统正经历着前所未有的变革。SOME/IP&#xff08;Scalable service-Oriented MiddlewarE over IP&#xff09;协议作为汽车电子通信领域的关键技术&#xff0c;其稳定性、可靠性与高效性对于整车性能的提升起着至关重…...

QT实现计算器

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

Go红队开发—语法补充

文章目录 错误控制使用自定义错误类型错误包装errors.Is 和 errors.Aspanic捕获、recover 、defer错误控制练习 接口结构体实现接口基本类型实现接口切片实现接口 接口练习Embed嵌入文件 之前有师傅问这个系列好像跟红队没啥关系&#xff0c;前几期确实没啥关系&#xff0c;因为…...

二、Redis 安装与基本配置:全平台安装指南 服务器配置详解

Redis 安装与基本配置:全平台安装指南 & 服务器配置详解 Redis 作为高性能的内存数据库,其安装和配置是使用 Redis 的第一步。本篇文章将全面介绍 Redis 的安装方式,覆盖 Windows、Linux、Docker 环境,并详细讲解 Redis 的基础配置,包括 持久化、日志、端口设置等。此…...

halcon学习笔记1

环境的搭建就不说了&#xff0c;主要是作者在入职后的实际学习与实践。 打开应用程序 这里作者的个人理解是1号区域主要是可以观察到读取的图像以及后续对图像进行何种操作&#xff0c;2的算子类似于Opencv中的API&#xff0c;可以在上面进行参数的调整&#xff0c;例如read_I…...

解决Docker拉取镜像超时错误,docker: Error response from daemon:

当使用docker pull或docker run时遇到net/http: request canceled while waiting for connection的报错&#xff0c;说明Docker客户端在访问Docker Hub时出现网络连接问题。可以不用挂加速器也能解决&#xff0c;linux不好用clash。以下是经过验证的方法&#xff08;感谢轩辕镜…...

Masscan下载Linux安装

masscan 是一款高速的端口扫描工具&#xff0c;能够在极短的时间内扫描大量IP地址和端口。以下是关于如何在Linux系统上下载并安装 masscan 的详细步骤。 ### 通过包管理器安装 对于一些Linux发行版&#xff0c;你可以直接使用系统的包管理器来安装 masscan。例如&#xff0c…...

js的简单介绍

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

神经网络 - 激活函数(Swish函数、GELU函数)

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

关于后端使用Boolean或boolean时前端收到的参数的区别

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

笔记:代码随想录算法训练营第35天: 01背包问题 二维、 01背包问题 一维 、LeetCode416. 分割等和子集

学习资料&#xff1a;代码随想录 这一块儿学得挺痛苦 注&#xff1a;文中含大模型生成内容 动态规划&#xff1a;01背包理论基础 卡码网第46题 思路&#xff1a;五部曲 定义&#xff1a;dp[i][j]为第i个物品背包容量为j&#xff0c;能装下的最大价值 递推公式&#xff1…...

安装 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 分别是什么&#xff1f;有什么关系&#xff1f; Servlet 是 java 官方定义的 web 开发的标准规范&#xff1b;Tomcat 是…...

OpenAI 模型攻克离散几何 80 年难题:Erdős 单位距离猜想被 AI 证明

OpenAI 模型攻克离散几何 80 年难题&#xff1a;Erdős 单位距离猜想被 AI 证明 一场改写数学史的AI突破 2026年5月20日&#xff0c;OpenAI 宣布其内部通用推理模型成功证明了一个困扰数学界近80年的开放问题——Erdős 单位距离问题&#xff08;Unit Distance Problem&#…...

Fastboot Enhance:Windows平台Android设备管理的终极图形化解决方案

Fastboot Enhance&#xff1a;Windows平台Android设备管理的终极图形化解决方案 【免费下载链接】FastbootEnhance A user-friendly Fastboot ToolBox & Payload Dumper for Windows 项目地址: https://gitcode.com/gh_mirrors/fa/FastbootEnhance Fastboot Enhance…...

Perplexity谚语查询功能实测报告:7类典型误用场景+5步精准调优法,错过即降效40%

更多请点击&#xff1a; https://kaifayun.com 第一章&#xff1a;Perplexity谚语查询功能的核心价值与适用边界 Perplexity 的谚语查询功能并非通用语言模型的简单问答接口&#xff0c;而是一个面向文化语义深度解析的专用能力模块。它依托高质量结构化谚语知识图谱与上下文感…...

别再硬套RBAC了!用Filebrowser的‘文件夹规则’搞定多级文件权限(附实战配置)

别再硬套RBAC了&#xff01;用Filebrowser的‘文件夹规则’搞定多级文件权限&#xff08;附实战配置&#xff09; 在权限管理的世界里&#xff0c;RBAC&#xff08;基于角色的访问控制&#xff09;早已成为行业标准&#xff0c;但你是否遇到过这样的场景&#xff1a;一个只有三…...

手把手教你用YOLOv5/PyTorch在DOTA V1.5数据集上训练自己的航拍目标检测模型

从零构建航拍目标检测模型&#xff1a;YOLOv5DOTA V1.5实战指南 当无人机镜头掠过城市上空&#xff0c;传回的40004000像素高清图像中&#xff0c;棒球场、港口集装箱、高速公路立交桥等目标如何被精准识别&#xff1f;本文将带您用YOLOv5框架&#xff0c;在包含18.8万实例的DO…...

别再自己造轮子了!手把手教你用LwRB环形缓冲区搞定嵌入式数据流(附DMA零拷贝实战)

嵌入式数据流处理的终极方案&#xff1a;LwRB环形缓冲区深度解析与DMA实战 在嵌入式开发中&#xff0c;数据流处理如同空气般无处不在却又容易被忽视。从UART接收到的传感器数据&#xff0c;到SPI传输的图像信息&#xff0c;再到I2C收集的设备状态&#xff0c;这些数据流的处理…...

linux文件基本操作作业(含文件基本操作的重点知识内容及截图)

文件基本操作 1 要求&#xff1a;请简要描述各操作所使用命令 文章目录文件基本操作查看文件新建和修改文件进入指定目录查看文件信息查找文件位置、指定内容内容排序、去除重复行统计创建目录文件的复制、移动和删除文件链接&#xff08;软/硬&#xff09; 查看文件 1、通过文…...

从游戏动作到影视特效:Blender Python骨骼动画脚本的跨界实战指南

从游戏动作到影视特效&#xff1a;Blender Python骨骼动画脚本的跨界实战指南 在数字内容创作领域&#xff0c;骨骼动画是连接游戏开发与影视特效的核心技术纽带。无论是独立游戏开发者需要将角色动作导出到Unity引擎&#xff0c;还是影视动画师希望批量处理动作捕捉数据&#…...

从缺页异常看Linux内存管理的基石:写时复制、延迟分配与交换机制

从缺页异常看Linux内存管理的基石&#xff1a;写时复制、延迟分配与交换机制 当你在Linux终端敲下./a.out时&#xff0c;内核如何将磁盘上的程序转化为内存中的鲜活进程&#xff1f;这个看似简单的过程背后&#xff0c;隐藏着一套精妙的内存管理机制。缺页异常&#xff08;Page…...

2026实测:如何把知网论文AI率从90%降到4%?(手把手教你降AI)

一份知网AIGC检测报告摆在面前&#xff1a;疑似度84.9%。几乎整篇论文都被标红&#xff0c;系统判定其中84.9%的内容"疑似AI生成"。这个数字意味着什么&#xff1f;意味着在任何一所高校的标准下&#xff0c;这篇论文都不可能通过审查。 然而同一篇论文经过处理后再次…...