Redis 中的过期策略和内存淘汰策略

news/2025/3/31 22:29:53/

过期策略

Redis 的 过期策略(Expiration Policy)决定了 如何管理和删除已过期的 key,确保内存资源的合理使用。Redis 提供了 三种过期策略

1. 惰性删除(Lazy Deletion)

特点:只有当客户端访问某个 key 时,Redis 才会检查它是否过期,过期则删除,否则继续保留。

工作方式

  • 当客户端执行 GETSETEXISTS 等操作访问某个 key 时,Redis 会检查该 key 是否过期。
  • 如果过期,则立即删除,并返回 nil(对于 GET 操作)。
  • 如果未过期,则正常返回 key 的值。

优点

✅ 访问少的过期 key 不会占用 CPU 资源进行扫描,减少 Redis 负担。
✅ 适用于对响应时间敏感的业务,因为删除操作分散到用户访问时进行。

缺点

❌ 过期 key 可能长时间占用内存,如果某些 key 过期但长期未访问,它们不会被删除,导致内存泄漏

2. 定期删除(Periodic Deletion)

特点:Redis 每隔一段时间主动扫描部分 key,删除已过期的 key,以减少内存占用。

工作方式

  • Redis 每 100 毫秒(默认)会随机抽取若干个设置了过期时间的 key 进行检查,并删除已过期的 key。
  • 但不会遍历所有 key,而是抽样检查,防止影响 Redis 性能。

优点

✅ 能够主动释放部分过期 key,避免内存无限增长。
✅ 相比惰性删除,更能减少过期 key 造成的内存泄漏

缺点

无法确保所有过期 key 都被及时删除,部分 key 仍可能滞留在内存
❌ 如果 key 过多,可能会有一定的过期 key 滞留,导致内存占用高于预期

3. 内存淘汰(Eviction)

特点:当 Redis 内存达到 maxmemory 上限时,触发内存淘汰策略,主动删除部分 key 以腾出空间。

工作方式

  • 当内存不足时,Redis 先尝试删除已过期的 key
  • 如果过期 key 不够,Redis 按照 maxmemory-policy 设定的策略(如 LRU、LFU、随机删除等)进行 key 淘汰。
  • 这部分策略与 Redis 内存淘汰策略(Eviction Policy) 相关。

优点

✅ 确保 Redis 不会因过多过期 key 占用内存而崩溃
✅ 配合 LRU/LFU 策略,可以更智能地淘汰低价值数据

缺点

❌ 只有当内存达到上限时才会触发,不适用于低内存占用场景
❌ 淘汰策略可能会删除未过期但仍然有用的数据,影响业务逻辑。

Redis 过期策略对比

过期策略触发时机作用优缺点
惰性删除访问 key 时仅在 key 被访问时检查并删除节省 CPU 资源,但可能导致内存泄漏
定期删除Redis 定时任务(默认 100ms 一次)随机扫描部分 key 并删除过期 key减少内存占用,但可能无法完全清理过期数据
内存淘汰内存达到 maxmemory 限制触发 maxmemory-policy 策略删除数据避免内存溢出,但可能删除未过期的重要数据

如何查看 Redis 过期 key 的统计信息?

Redis 提供了 INFO 命令,可以查看过期 key 相关的统计信息:

redis-cli INFO stats | grep expired

示例返回:

expired_keys:12345      # 说明有 12345 个 key 过期并被删除
expired_stale_perc:10.56 # 说明大约 10.56% 的 key 超过 TTL 仍未被删除

如何优化 Redis 过期 key 处理?

1️⃣ 尽量使用合适的 maxmemory-policy

  • 缓存业务(如热点数据):建议 allkeys-lru,删除最近最少使用的 key。
  • 严格控制过期 key:建议 volatile-ttl,优先删除即将过期的数据。

2️⃣ 手动清理过期 key
如果系统发现过期 key 过多,可以主动执行:

redis-cli keys "*" | xargs redis-cli del

警告keys "*" 在大数据量时可能会导致 Redis 停滞,应谨慎使用。

3️⃣ 使用 SCAN 替代 KEYS

redis-cli --scan --pattern "prefix:*" | xargs redis-cli del

SCAN 命令不会阻塞 Redis,可以安全地遍历 key 并删除过期数据。

总结

  • Redis 采用 惰性删除 + 定期删除 + 内存淘汰 的组合策略来管理过期 key。
  • 惰性删除:访问 key 时才检查是否过期,节省 CPU 但可能导致内存泄漏。
  • 定期删除:Redis 定期随机检查并删除部分过期 key,减少过期 key 堆积问题。
  • 内存淘汰:当内存不足时触发,配合 maxmemory-policy 策略删除 key。
  • 优化方案
    • 短期热点数据(如缓存):推荐 allkeys-lru
    • 严格控制 key 过期:推荐 volatile-ttl
    • 手动定期清理过期数据(如 SCAN + DEL)。

如果你的业务对内存敏感,建议结合 maxmemory-policy 进行优化,以确保 Redis 在高并发下依然能够稳定运行。 🚀

内存淘汰策略

Redis 的内存淘汰策略(Eviction Policy)主要用于在内存达到上限(maxmemory 配置的限制)时决定如何删除数据,以便腾出空间存储新数据。Redis 提供了多种淘汰策略,可通过 maxmemory-policy 参数进行配置。常见策略如下:

1. volatile-lruLeast Recently Used,最近最少使用

  • 只对设置了过期时间(TTL)的 key 进行 LRU 淘汰。
  • 当内存不足时,优先淘汰最近最少使用(LRU)的 key。

2. allkeys-lru淘汰所有 key,LRU

  • 所有 key(无论是否有过期时间)都可以参与淘汰。
  • 当内存不足时,Redis 根据 LRU 算法淘汰最近最少使用的 key。

3. volatile-lfuLeast Frequently Used,最近最少使用频率

  • 仅针对设置了过期时间的 key 进行 LFU 淘汰。
  • 淘汰访问频率(访问次数最少)的 key。

4. allkeys-lfu淘汰所有 key,LFU

  • 所有 key(无论是否有过期时间)都可以参与淘汰。
  • Redis 根据 LFU 算法淘汰访问次数最少的 key。

5. volatile-random随机淘汰

  • 仅针对设置了过期时间的 key 进行随机淘汰

6. allkeys-random所有 key 随机淘汰

  • 所有 key 都可以被随机淘汰,而不考虑 LRU 或 LFU。

7. volatile-ttl根据 TTL 淘汰

  • 仅针对设置了过期时间的 key 进行淘汰。
  • 优先淘汰TTL 最短的 key(即最早过期的 key)。

8. noeviction拒绝新写入

  • 不淘汰任何 key,而是直接返回错误(如 OOM command not allowed)。
  • 适用于严格控制数据不被删除的场景,比如缓存预热或全量数据存储。

如何选择合适的淘汰策略?

策略适用场景
volatile-lru适用于缓存,只淘汰可过期的 key,保留重要数据。
allkeys-lru适用于高性能缓存,确保经常访问的数据优先保留。
volatile-lfu适用于访问频率不均衡的缓存,淘汰低频访问的过期 key。
allkeys-lfu适用于热点数据,确保最常访问的数据留存。
volatile-random适用于非热点数据缓存,随机清理部分过期数据。
allkeys-random适用于数据无访问优先级的场景,如消息队列。
volatile-ttl适用于短时缓存,如临时验证码存储。
noeviction适用于严格限制数据丢失的场景,如用户 session 数据。

如何查看 Redis 当前的淘汰策略?

使用 Redis 命令:

redis-cli config get maxmemory-policy

如何修改 Redis 淘汰策略?

  • 临时修改(重启后失效):
    redis-cli config set maxmemory-policy allkeys-lru
    
  • 永久修改(写入 redis.conf 配置文件):
    maxmemory-policy allkeys-lru
    

LRU 与 LFU 的区别

算法计算方式适用场景
LRU(Least Recently Used)淘汰最近最少使用的 key适用于时间相关的缓存,如热点新闻。
LFU(Least Frequently Used)淘汰访问次数最少的 key适用于长期热门数据,如 API 访问统计。

如果你的应用需要保留最常访问的数据,建议选择 LFU;如果需要保留最近使用的数据,建议选择 LRU

总结

  • Redis 支持 8 种内存淘汰策略,可根据业务场景选择合适的策略。
  • LRU 适用于短期热点缓存,LFU 适用于长期热点缓存。
  • volatile- 前缀的策略仅淘汰设置了过期时间的 key,而 allkeys- 策略适用于所有 key。
  • noeviction 适用于不希望数据被自动删除的业务,如用户登录状态管理。

如果你的业务主要是缓存,推荐使用 allkeys-lruvolatile-lru,这样能尽可能保留高价值的数据。

LRU 示例代码

下面是一个使用 Java 实现 LRU(Least Recently Used,最近最少使用)算法的示例代码:

这个实现使用了 LinkedHashMap,它自带维护元素插入顺序或访问顺序的功能,非常适合用来实现 LRU 缓存。我们可以通过重写 removeEldestEntry 方法来限制缓存的大小并删除最不常用的元素。

import java.util.LinkedHashMap;
import java.util.Map;public class LRUCache<K, V> {private final int capacity;private final Map<K, V> cache;// 构造函数public LRUCache(int capacity) {this.capacity = capacity;// 使用 LinkedHashMap 以便维护插入顺序this.cache = new LinkedHashMap<>(capacity, 0.75f, true);}// 获取缓存中的值public V get(K key) {return cache.getOrDefault(key, null);  // 返回值,若不存在则返回 null}// 将值插入缓存public void put(K key, V value) {// 如果缓存已满,移除最老的元素if (cache.size() >= capacity) {K eldestKey = cache.keySet().iterator().next();cache.remove(eldestKey);}cache.put(key, value);  // 插入或更新缓存}// 打印缓存内容public void printCache() {System.out.println(cache);}// 测试 LRU 算法public static void main(String[] args) {LRUCache<Integer, String> lruCache = new LRUCache<>(3); // 容量为 3// 插入数据lruCache.put(1, "One");lruCache.put(2, "Two");lruCache.put(3, "Three");lruCache.printCache();  // 输出 {1=One, 2=Two, 3=Three}// 访问 key 2lruCache.get(2);lruCache.printCache();  // 输出 {1=One, 3=Three, 2=Two},key 2 最近访问,移动到最后// 插入新的数据,超出容量lruCache.put(4, "Four");lruCache.printCache();  // 输出 {3=Three, 2=Two, 4=Four},key 1 被淘汰// 插入新的数据,超出容量lruCache.put(5, "Five");lruCache.printCache();  // 输出 {2=Two, 4=Four, 5=Five},key 3 被淘汰}
}

代码解析

  1. 构造函数:使用 LinkedHashMap 来存储缓存数据,true 参数保证了访问顺序,最近访问的元素会被移到末尾。
  2. get 方法:获取缓存中的元素,如果元素不存在返回 null
  3. put 方法:插入数据。如果缓存已满,使用 LinkedHashMapkeySet().iterator().next() 获取最老的元素并移除,确保缓存大小始终不超过指定的容量。
  4. printCache 方法:用于打印当前缓存内容,以便在测试中查看结果。

示例输出

{1=One, 2=Two, 3=Three}
{1=One, 3=Three, 2=Two}
{3=Three, 2=Two, 4=Four}
{2=Two, 4=Four, 5=Five}

解释

  1. 初始缓存包含了 1=One, 2=Two, 3=Three
  2. 访问 key 2 后,key 2 移动到缓存末尾,成为最近使用的项。
  3. 插入新的元素 4=Four 时,由于缓存容量为 3,key 1 被淘汰。
  4. 插入新的元素 5=Five 时,key 3 被淘汰,因为它是最久未访问的元素。

扩展

如果你需要更复杂的 LRU 缓存逻辑(比如支持线程安全的操作),你可以通过使用 ConcurrentHashMap 和同步机制来实现。

LFU 示例代码

LFU(Least Frequently Used,最不常用)算法是根据数据的访问频率来管理缓存的,最不常访问的数据会被优先淘汰。实现 LFU 算法时,需要记录每个缓存项的访问次数,并根据访问频率来决定哪个元素最先被移除。

以下是用 Java 实现 LFU 缓存算法的示例代码:

import java.util.*;public class LFUCache<K, V> {// 缓存最大容量private final int capacity;// 存储缓存数据 (key -> value)private final Map<K, V> cache;// 存储缓存的访问次数 (key -> frequency)private final Map<K, Integer> frequencyMap;// 存储访问频率到 key 列表的映射 (frequency -> key list)private final Map<Integer, LinkedHashSet<K>> frequencyList;// 记录当前最小频率private int minFrequency;// 构造函数public LFUCache(int capacity) {this.capacity = capacity;this.cache = new HashMap<>();this.frequencyMap = new HashMap<>();this.frequencyList = new HashMap<>();this.minFrequency = 0;}// 获取缓存的值public V get(K key) {if (!cache.containsKey(key)) {return null;}// 更新频率int frequency = frequencyMap.get(key);frequencyMap.put(key, frequency + 1);// 更新频率列表frequencyList.get(frequency).remove(key);if (frequencyList.get(frequency).isEmpty() && frequency == minFrequency) {minFrequency++;}frequencyList.computeIfAbsent(frequency + 1, k -> new LinkedHashSet<>()).add(key);return cache.get(key);}// 放入缓存public void put(K key, V value) {if (capacity <= 0) {return;}if (cache.containsKey(key)) {cache.put(key, value);get(key); // 更新频率return;}if (cache.size() >= capacity) {evict();}cache.put(key, value);frequencyMap.put(key, 1);frequencyList.computeIfAbsent(1, k -> new LinkedHashSet<>()).add(key);minFrequency = 1;}// 删除最不常用的元素private void evict() {LinkedHashSet<K> leastFrequentKeys = frequencyList.get(minFrequency);K keyToEvict = leastFrequentKeys.iterator().next();leastFrequentKeys.remove(keyToEvict);if (leastFrequentKeys.isEmpty()) {frequencyList.remove(minFrequency);}cache.remove(keyToEvict);frequencyMap.remove(keyToEvict);}// 打印缓存内容public void printCache() {System.out.println(cache);}// 测试 LFU 算法public static void main(String[] args) {LFUCache<Integer, String> lfuCache = new LFUCache<>(3); // 容量为 3// 插入数据lfuCache.put(1, "One");lfuCache.put(2, "Two");lfuCache.put(3, "Three");lfuCache.printCache();  // 输出 {1=One, 2=Two, 3=Three}// 访问 key 1 和 key 2lfuCache.get(1);lfuCache.get(2);lfuCache.printCache();  // 输出 {1=One, 2=Two, 3=Three},key 1 和 key 2 的频率增加// 插入新的数据,超出容量lfuCache.put(4, "Four");lfuCache.printCache();  // 输出 {1=One, 2=Two, 4=Four},key 3 被淘汰,因为其访问频率最低// 插入新的数据,超出容量lfuCache.put(5, "Five");lfuCache.printCache();  // 输出 {2=Two, 4=Four, 5=Five},key 1 被淘汰,因为其访问频率最低}
}

代码解析

  1. cache:存储缓存的实际数据,key -> value 映射。
  2. frequencyMap:记录每个缓存项的访问频率,key -> frequency 映射。
  3. frequencyList:使用一个 LinkedHashSet 来存储具有相同访问频率的缓存项,frequency -> key list 映射。这样可以方便地找到哪些缓存项访问频率相同。
  4. minFrequency:用于记录当前缓存中最小的访问频率,用来优化缓存淘汰时选择最少使用的元素。
  5. get 方法:返回缓存项的值并更新该项的访问频率。
  6. put 方法:插入新数据,若缓存已满,则调用 evict() 方法淘汰最不常用的元素。
  7. evict 方法:淘汰最不常用的元素(即最少频率的元素),并从 cachefrequencyMap 中移除。
  8. printCache 方法:打印当前缓存的内容。

示例输出

{1=One, 2=Two, 3=Three}
{1=One, 2=Two, 3=Three}
{1=One, 2=Two, 4=Four}
{2=Two, 4=Four, 5=Five}

解释

  1. 初始化缓存容量为 3,插入 1=One, 2=Two, 3=Three
  2. 访问 key 1key 2 后,key 1key 2 的访问频率增加,key 3 的访问频率最低。
  3. 插入 key 4 时,key 3 被淘汰,因为它的访问频率最低。
  4. 插入 key 5 时,key 1 被淘汰,因为它的访问频率最低。

总结

LFU 缓存算法根据元素的访问频率来进行缓存淘汰,最不常访问的元素最先被删除。这个实现通过 frequencyMapfrequencyList 有效地跟踪访问频率,并通过 evict 方法淘汰最不常用的缓存项。


http://www.ppmy.cn/news/1583959.html

相关文章

YOLO历代发展 图像增强方式 架构

YOLO1 YOLOV5 数据增强 mosaic 仿射变换(Affine)、透视变换(Perspective) 网络搭建

使用idea开发spark程序

新建scala 项目 创建lib目录 将spark jars/ 路径下所有jar 复制到 lib目录 添加依赖 创建scala 程序 package sparkimport org.apache.spark.{SparkConf, SparkContext}object WordCount {def main(args: Array[String]): Unit {val conf new SparkConf().setAppName(&q…

【STM32】初识STM32

目录 1、 什么是STM321.1、STM32的字面含义1.2、STM32诞生的背景 2、STM32能做什么2.1、STM32有什么2.2、身边常见的电子产品 3、STM32怎么选型3.1、STM32分类3.2、ST的32位微控制器产品线3.2.1、STM32F103VET6 3.3、STM32的命名方法3.4、如何分配原理图引脚3.5、数据手册中对引…

数据可视化TensorboardX和tensorBoard安装及使用

tensorBoard 和TensorboardX 安装及使用指南 tensorBoard 和 TensorBoardX 是用于可视化机器学习实验和模型训练过程的工具。TensorBoard 是 TensorFlow 官方提供的可视化工具&#xff0c;而 TensorBoardX 是其社区驱动的替代品&#xff0c;支持 PyTorch 等其他框架。以下是它…

【HTML5游戏开发教程】零基础入门合成大西瓜游戏实战 | JS物理引擎+Canvas动画+完整源码详解

《从咖啡杯到财务自由&#xff1a;一个程序员的合成之旅——当代码遇上物理引擎的匠心之作》 &#x1f31f; 这是小游戏开发系列的第四篇送福利文章&#xff0c;感谢一路以来支持和关注这个项目的每一位朋友&#xff01; &#x1f4a1; 文章力求严谨&#xff0c;但难免有疏漏之…

无需docker三步安装deepseek可视化操作软件-Open-WebUI

在以前安装Open-WebUI时&#xff0c;需要通过docker安装, 针对小白来讲呢有些麻烦, 因此这里推荐使用python环境安装Open-WebUI,简单快捷上手快! 1. Mac安装python3.11 以上的环境, windows同学直接官网下载安装包msi,双击安装即可1.1 Mac直接安装 python3.11brew install pyt…

小迪安全109-php模型动态调试,反序列化,变量覆盖,tp框架,原生pop链

案例一 原生代码变量覆盖&#xff0c;duomicms 变量覆盖是什么 这个时候为什么会输出xiaodi呢 就是因为$ab所以$$a$b 所以就让$bxiaodi 搞不到源码&#xff0c;看老师的学吧 这一般在web端页面是看不到&#xff0c;也不好发现&#xff0c;代码可以看关键字和函数 例如$$这…

Flink介绍与安装

Apache Flink是一个在有界数据流和无界数据流上进行有状态计算分布式处理引擎和框架。Flink 设计旨在所有常见的集群环境中运行&#xff0c;以任意规模和内存级速度执行计算。 一、主要特点和功能 1. 实时流处理: 低延迟: Flink 能够以亚秒级的延迟处理数据流&#xff0c;非常…