缓存淘汰策略:Redis中的内存管理艺术

在现代应用架构中,缓存是提升性能的关键组件。

Redis,作为一个高性能的键值存储系统,因其快速的数据访问能力而被广泛使用。然而,由于物理内存的限制,Redis必须在存储空间和性能之间找到平衡,这就引出了缓存淘汰策略的重要性。

为什么要淘汰

比如说线上偶尔会遇到本地缓存了太多数据,导致应用内存不足的问题。

像Java这样具有自动垃圾回收功能的编程语言时,如果内存不足,就可能频繁触发垃圾回收过程,甚至触发完整的垃圾回收(full GC),影响应用程序的性能。所以使用缓存肯定要控制住缓存的内存使用量

当达到了内存使用上限,但是又需要加入新的键值对时,怎么办?

最保守的做法就是直接报错,那么就没有办法缓存新的数据了。后续如果缓存中已有的数据过期了,才能缓存新的数据。

但是对于大多数的业务来说,已经在缓存中的数据可能用不上了,虽然还没有过期,但是可以考虑淘汰掉,腾出空间来存放新的数据,这些新的数据比老的数据有更大的可能性被使用。

淘汰策略

缓存淘汰策略是指当缓存达到其最大容量时,决定哪些数据应该被移除以腾出空间给新数据的机制。Redis提供了多种缓存淘汰策略,每种策略都有其特定的使用场景和性能影响。

noeviction(默认策略)

noeviction策略简单地拒绝所有写入操作,当内存达到限制时,新的写入请求会被返回错误。这种策略适用于那些不能容忍数据丢失,且写入请求较少的场景。

allkeys-lru

allkeys-lru策略会从所有键中选择最近最少使用的键进行淘汰。这种策略适用于那些对所有数据的访问频率大致相同的场景,例如,缓存静态数据。

volatile-lru

volatile-lru策略仅从设置了过期时间的键中选择最近最少使用的键进行淘汰。这种策略适用于那些对过期数据敏感,且希望在内存不足时优先移除即将过期数据的场景。

allkeys-random

allkeys-random策略从所有键中随机选择键进行淘汰。这种策略适用于那些对数据的访问模式没有特定偏好,且希望以随机方式淘汰数据的场景。

volatile-random

volatile-random策略从设置了过期时间的键中随机选择键进行淘汰。这种策略适用于那些希望在内存不足时随机移除即将过期数据的场景。

volatile-ttl

volatile-ttl策略从设置了过期时间的键中选择即将过期的键进行淘汰。这种策略适用于那些希望在内存不足时优先移除即将过期数据的场景,但与volatile-lru相比,它更倾向于移除那些剩余时间最短的数据。

LRU

LRU(Least Recently Used)是指最近最少使用算法。当缓存容量不足的时候,就从所有的 key 里面挑出一个最近一段时间最长时间未使用的 key淘汰掉。

在实践中,一般是优先考虑 LRU,因为 LRU 对时间局部性突出的应用非常友好,而大多数的应用场景都满足时间局部性的要求。

时间局部性是指最近被访问过的数据在未来也很可能被再次访问的特性。大多数应用场景都具有这种特性,因此LRU算法能够有效地保留最近被访问的数据,淘汰那些长时间未被访问的数据。

弊端

但是 LRU 在一些特殊的场景下,表现的也不好。

(1)访问历史记录

因为越是历史悠久的,越有可能已经被淘汰了。

(2)遍历

在遍历过程中,LRU算法可能会保留当前遍历到的对象,因为这些对象最近被访问过。但实际上,已经被遍历过的对象可能应该被淘汰,以便为尚未遍历到的对象腾出空间。这可能导致缓存命中率降低,因为缓存中保留了很多已经不需要的数据,而需要的数据却没有被缓存。

在这里插入图片描述

例如,图中遍历到 k5 的时候会触发淘汰,把 k4 淘汰了。紧接着遍历 k4,会把 k3 淘汰了。以此类推,最终结果就是缓存完全没命中。

实现

利用链表把 key 连起来,然后每次被访问到的 key 都挪到队尾,那么队首就是最近最长时间未访问过的 key,当缓存容量不足时,就从队首开始淘汰 key。

比如借助 Java 的 LinkedHashMap 去实现 LRU 算法。

public class LRUCache<K, V> extends LinkedHashMap<K, V> {private final int capacity;// 调用LinkedHashMap的构造函数public LRUCache(int capacity) {// 设置一个适当的负载因子以减少rehash操作// 第三个参数true表明LinkedHashMap按照访问顺序来排序,最近访问的在尾部,最老访问的在头部super(capacity, 0.75f, true);this.capacity = capacity;}@Overrideprotected boolean removeEldestEntry(Map.Entry<K, V> eldest) {// 当Map中数据量大于指定缓存个数的时候,返回true,自动删除最老的数据return size() > capacity;}// 提供get和put方法public V get(Object key) {return super.get(key);}public V put(K key, V value) {return super.put(key, value);}// 打印所有缓存中的项public void printCache() {super.entrySet().forEach(entry -> System.out.println(entry.getKey() + " : " + entry.getValue()));}  
}

自定义淘汰策略

比如说某个服务同时服务于 VIP 用户和普通用户,那么完全可以在缓存触发淘汰的时候,先把普通用户的数据淘汰了。所以可以考虑为每一个键值对绑定一个优先级,每次缓存要执行淘汰的时候,就从先淘汰优先级最低的数据。

利用有序集合控制键值对数量,并且按照优先级来淘汰键值对。这个有序集合是使用数据的优先级来排序的,也就是用优先级作为 score

增加一个键值对就要执行一个 lua 脚本。在这个脚本里面,它会先检测有序集合里面的元素个数有没有超过允许的键值对数量上限,如果没有超过,就写入键值对,再把 key 加入有序集合。如果超过了上限,那么就从有序集合里面拿出第一个 key,删除这个 key 对应的键值对。

同时监听 Redis 上的删除事件,每次收到删除事件,就把有序集合中对应的 key 删除。

-- Lua 脚本用于自定义淘汰策略
-- KEYS[1] 是缓存中的key
-- KEYS[2] 是缓存中的有序集合的key
-- ARGV[1] 是新的值
-- ARGV[2] 优先级--1. 先判断有序集合中的 key 数量
local nums = redis.call('ZCARD', KEYS[2])if tonumber(nums) >= 3 then-- 代表有序集合容量已到上限,开始删除优先级最低的那个元素local members = redis.call('ZRANGE', KEYS[2], 0, 0, 'WITHSCORES')-- 检查有序集合是否为空if members and tonumber(members) > 1 then-- 获取分数最低的成员(第一个成员的分数)local lowest_member_score = members[2]local lowest_member = members[1]-- 执行ZREM命令删除分数最低的成员redis.call('ZREM', KEYS[2], lowest_member)redis.call('DEL', lowest_member)else-- 如果有序集合为空或不存在成员,则返回空return nilend
end--2. 开始设置 key,和有序集合的元素
local result = redis.call('SET', KEYS[1], ARGV[1])
result = redis.call('ZADD', KEYS[2], ARGV[2], KEYS[1])return result

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AUTOSAR COM 与 LargeDataCOM 模块解析及 C++ 实现示例

AUTOSAR COM 和 LargeDataCOM 模块在功能和使用场景上有一些显著的区别。以下是它们的主要区别及具体的应用示例,最后用 C++ 源代码来解析说明。 AUTOSAR COM 模块 • 功能:主要用于处理标准大小的信号和 I-PDU(协议数据单元),提供了信号打包、解包、数据传输和接收等功能…

JavaWeb复习

在网络应用程序中有两种基本的结构&#xff0c;即C/S和B/S&#xff0c;对于c/s程序分为客户机和服务器两层&#xff0c;把应用软件按照在客户机端(通常由客户端维护困难)&#xff0c;通过网络与服务器进行相互通信。B/S结构却不用通知客户端安装某个软件&#xff0c;内容修改了…

qt获取本机IP和定位

前言&#xff1a; 在写一个天气预报模块时&#xff0c;需要一个定位功能&#xff0c;在网上翻来翻去才找着&#xff0c;放在这里留着回顾下&#xff0c;也帮下有需要的人 正文&#xff1a; 一开始我想着直接调用百度地图的API来定位&#xff0c; 然后我就想先获取本机IP的方…

python爬取旅游攻略(1)

参考网址&#xff1a; https://blog.csdn.net/m0_61981943/article/details/131262987 导入相关库&#xff0c;用get请求方式请求网页方式&#xff1a; import requests import parsel import csv import time import random url fhttps://travel.qunar.com/travelbook/list.…

Oracle OCP认证考试考点详解082系列12

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 56. 第56题&#xff1a; 题目 解析及答案&#xff1a; 关于企业管理器&#xff08;EM&#xff09;Express&#xff0c;以下哪两个陈述是…

Postgresql源码(137)执行器参数传递与使用

参考 《Postgresql源码&#xff08;127&#xff09;投影ExecProject的表达式执行分析》 0 总结速查 prepare p_04(int,int) as select b from tbl_01 where a $1 and b $2为例。 custom计划中&#xff0c;在表达式计算中使用参数的值&#xff0c;因为custom计划会带参数值&…

SPI通信详解-学习笔记

参考原文地址 SPI&#xff1a;高速、全双工&#xff0c;同步、通信总线 SPI主从模式 SPI分为主、从两种模式&#xff0c;一个SPI通讯系统需要包含一个&#xff08;且只能是一个&#xff09;主设备&#xff0c;一个或多个从设备。提供时钟的为主设备&#xff08;Master&#xff…

Day102漏洞发现-漏扫项目篇Poc开发Yaml语法插件一键生成匹配结果交互提取

知识点&#xff1a; 1、Nuclei-Poc开发-环境配置&编写流程 2、Nuclei-Poc开发-Yaml语法&匹配提取 3、Nuclei-Poc开发-BurpSuite一键生成插件 Nuclei-Poc开发-环境配置&编写流程 1、开发环境&#xff1a;VscodeYaml插件 Visual Studio Code - Code Editing. R…

Node.js 入门指南:从零开始构建全栈应用

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;node.js篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来node.js篇专栏内容:node.js-入门指南&#xff1a;从零开始构建全栈应用 前言 大家好&#xff0c;我是青山。作…

WordPress网站添加嵌入B站视频,自适应屏幕大小,取消自动播放

结合bv号 改成以下嵌入式代码&#xff08;自适应屏幕大小,取消自动播放&#xff09; <iframe style"width: 100%; aspect-ratio: 16/9;" src"//player.bilibili.com/player.html?isOutsidetrue&bvidBV13CSVYREpr&p1&autoplay0" scrolling…

大模型应用系列:Query 变换的示例浅析

【引】NLP中的经典组件在大模型应用中还有效么&#xff1f;大模型对自然语言处理中的典型任务有什么影响么&#xff1f; RAG应用通过分割文档、嵌入向量化并检索高语义相似性的块来响应用户问题&#xff0c;但面临文档块不相关、用户用词不当及结构化查询需求等问题。若RAG无法…

【Oracle】空格单字符通配符查询匹配失败

问题 在进行模糊查询的时候&#xff0c;通过全局任意字符串匹配出含有两个字刘姓的人&#xff0c;但是通过刘_不能匹配出结果。 解决 检查后发现&#xff0c;姓名中包含空格 SELECT * FROM student WHERE TRIM(sname) LIKE 刘_;第一种解决方案就是查询的时候进行去空格处理&a…

讲讲⾼并发的原则?

大家好&#xff0c;我是锋哥。今天分享关于【讲讲⾼并发的原则&#xff1f;】面试题。希望对大家有帮助&#xff1b; 讲讲⾼并发的原则&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 高并发是指系统在同一时间内能够处理大量请求的能力。要有效地管理…

鸿蒙进阶-AlphabetIndexer组件

大家好&#xff0c;这里是鸿蒙开天组&#xff0c;今天我们来学习AlphabetIndexer组件&#xff0c;喜欢就点点关注吧&#xff01; 通过 AlphabetIndexer 组件可以与容器组件结合&#xff0c;实现导航联动&#xff0c;以及快速定位的效果 核心用法 AlphabetIndexer不是容器组件…

ubuntu交叉编译expat库给arm平台使用

1.下载expat库源码: https://github.com/libexpat/libexpat/release?page=2 wget https://github.com/libexpat/libexpat/release/download/R_2_3_0/expat-2.3.0.tar.bz2 下载成功: 2.解压expat库,并进入解压后的目录: tar xjf expat-2.3.0.tar.bz2 cd expat-2.3.0 <…

【系统面试篇】进程和线程类(1)(笔记)——区别、通讯方式、同步、互斥、锁分类

目录 一、问题综述 1. 进程和线程的区别&#xff1f; 2. 进程的状态有哪些&#xff1f; 3. 进程之间的通信方式? &#xff08;1&#xff09;管道 &#xff08;2&#xff09;消息队列 &#xff08;3&#xff09;共享内存 &#xff08;4&#xff09;信号量 &#xff08…

nginx(四):如何在 Nginx 中配置以保留真实 IP 地址

如何在 Nginx 中配置以保留真实 IP 地址 1、概述2、nginx配置示例2.1、配置说明2.2、客户端获取真实IP2.2.1、代码说明 3、插曲4、总结 大家好&#xff0c;我是欧阳方超&#xff0c;可以我的公众号“欧阳方超”&#xff0c;后续内容将在公众号首发。 1、概述 当使用nginx作为…

C++《list的模拟实现》

在上一篇C《list》专题当中我们了解了STL当中list类当中的各个成员函数该如何使用&#xff0c;接下来在本篇当中我们将试着模拟实现list&#xff0c;在本篇当中我们将通过模拟实现list过程中深入理解list迭代器和之前学习的vector和string迭代器的不同&#xff0c;接下来就开始…

本篇万字,博客最细,oled多级菜单代码解析,与实现教程,指针实现(含源码)!!!

目录 教程前言 多级菜单基本知识 驱动文件创建 ​编辑 ​编辑 ​编辑 定义菜单数据类型代码解析 按键代码解析 菜单数据赋值代码解析 菜单按键切换显示代码解析 项目工程移植地址 教程前言 前言&#xff1a;编写不易&#xf…

华为HarmonyOS打造开放、合规的广告生态 - 贴片广告

场景介绍 贴片广告是一种在视频播放前、视频播放中或视频播放结束后插入的视频或图片广告。 接口说明 接口名 描述 loadAd(adParam: AdRequestParams, adOptions: AdOptions, listener: AdLoadListener): void 请求单广告位广告&#xff0c;通过AdRequestParams、AdOptions…