在这里祝大家新年快乐🎆🎉,欢迎来到 Redis 进化日志 的第三天。

前两天我们把 Redis 当作一个“无限容量”的超级便签在使用,存数据存得很爽。但回到现实,不管是你那台 16G 内存的笔记本,还是公司里昂贵的服务器,内存永远是最昂贵的资源

如果不加控制地一直往 Redis 里 SET 数据,迟早有一天内存会爆满。

很多同学在做课设时没感觉,因为数据量少。但在面试或者实习中,面试官最爱问这种极端场景:

“如果 Redis 内存满了,我再往里写数据,它是会死机?会报错?还是会偷偷删掉旧数据?”

“我设置了 1 分钟过期的 Key,1 分钟到了它真的立马从内存消失了吗?”

这两个问题,一个关乎空间(内存淘汰),一个关乎时间(过期策略)。今天这一篇,咱们就把 Redis 底层的“断舍离”机制彻底扒清楚。这是一道分水岭,跨过去,你就不是“只会调包”的小白了。


1. 内存的红线:maxmemory

首先,Redis 并不是无底洞。在 Redis 的配置文件 redis.conf 中,有一个至关重要的参数叫 maxmemory

1.1 默认是“无限”的吗?

  • 在 64 位系统下(现在的服务器基本都是):默认 maxmemory 是 0,也就是不限制

  • 这就意味着,只要物理内存够,Redis 就会一直吃。如果你的服务器有 32G 内存,Redis 能吃到 30G+,直到操作系统察觉到危险,触发 OOM Killer(内存溢出杀手)把 Redis 进程强行杀掉。

1.2 生产环境怎么做?

为了不让 Redis 把整个服务器搞挂,我们通常会设置一个安全阈值。比如一台 4GB 的服务器,我们可能会分配 3GB 给 Redis。

你可以在配置文件里改,也可以直接在命令行动态设置(立即生效):

# 设置最大内存为 100MB
CONFIG SET maxmemory 100mb

那么问题来了:当数据量达到 100MB 这一刻,我要再塞进一个新的 Key,Redis 会怎么办?

这就涉及到了第一道防线:内存淘汰策略


2. 内存淘汰:谁是那个“倒霉蛋”?

当内存用量达到 maxmemory 时,Redis 必须做出选择:是拒绝新客,还是踢走旧人?

Redis 官方提供了 8 种策略(Redis 4.0 后),但作为初学者,你只需要把最核心的 3 种逻辑刻在脑子里:

① 摆烂模式:NoEviction(默认配置)

  • 逻辑:内存满了?那就不许写新数据了!

  • 后果:任何尝试申请内存的写命令(如 SET、LPUSH、HSET)都会直接报错:

    (error) OOM command not allowed when used memory > 'maxmemory'.

    但读命令(GET)以及删除命令(DEL)依然正常工作。

  • 评价:这是一种保证数据绝对安全、但不保证可用性的策略。做缓存千万别用这个,否则你的系统会因为缓存写不进去而疯狂报错。

② 优先扔掉“最没用”的:AllKeys-LRU(缓存首选)

这是最主流、最推荐的策略。

  • LRU (Least Recently Used):翻译过来叫“最近最少使用”。

  • 逻辑:Redis 会“盯着”所有的 Key(AllKeys),看谁最近最长时间没被访问过。

    • Key A:1 秒前被 GET 过。

    • Key B:3 天前被 GET 过。

    • 内存满了,Key B 卒。

  • 场景:绝大多数缓存场景。因为如果一个数据很久没人查了,说明它是冷数据,删了也不心疼,把位置腾给热数据。

③ 只扔“设了过期时间”的:Volatile-LRU

  • 逻辑:它也会用 LRU 算法,但它有一个原则:只动那些设置了过期时间(TTL)的 Key。至于那些没设置过期的“永久数据”,打死也不删,哪怕内存爆了报错也不删。

  • 场景:如果你在 Redis 里混用了“临时缓存”和“重要数据”(比如配置参数、白名单),用这个策略可以保住重要数据不被误删。

学长笔记:

面试官有时候会追问:“LRU 有什么缺点?了解 LFU 吗?”

  • LRU 的 Bug:如果你扫了一次全表(全量查询),把很多冷数据读了一遍,LRU 就会误以为它们是热数据,反而把真正的热数据挤走了。

  • LFU (Least Frequently Used):它是按“访问频率”删的。哪怕你刚才读了一次,但如果历史访问次数很低,依然会被优先淘汰。这是 Redis 4.0 后更智能的选择。

这里附带Redis所有常见的缓存淘汰策略

目前 Redis 主要提供 8 种 淘汰策略,可以根据数据的 过期时间(TTL)访问频率(LRU/LFU) 划分为三大类:


2.1. 不进行淘汰

  • noeviction(默认策略):当内存达到限制时,Redis 不会删除任何数据。它会对所有写入操作(如 SETLPUSH)报错,但允许读取操作。

    适用场景: 业务不允许数据丢失,且能够通过外部监控主动扩容或清理。


2.2 对设置了过期时间的数据进行淘汰 (Volatile)

这类策略只会在设置了 EXPIRE 的 Key 中进行筛选。

  • volatile-lru:在设置了过期时间的数据中,删除 最近最少使用(Least Recently Used)的数据。

  • volatile-lfu:在设置了过期时间的数据中,删除 最不经常使用(Least Frequently Used)的数据。

  • volatile-ttl:从设置了过期时间的数据中,优先删除 剩余存活时间(TTL)最短 的数据。

  • volatile-random:从设置了过期时间的数据中,随机 删除。


2.3 对所有数据进行淘汰 (Allkeys)

这类策略不看是否设置了过期时间,而是针对全体数据进行筛选。

  • allkeys-lru:从所有数据中,删除 最近最少使用 的数据。

  • allkeys-lfu:从所有数据中,删除 最不经常使用 的数据。

  • allkeys-random:从所有数据中,随机 删除。


核心算法:LRU vs LFU

为了更直观地理解如何选择,我们需要区分这两个核心算法的区别:

算法 全称 核心逻辑 侧重点
LRU Least Recently Used 记录最后一次访问的时间戳。 时间: 淘汰掉很久没被访问的数据。
LFU Least Frequently Used 记录访问次数计数器。 频率: 淘汰掉访问次数最少的数据。

如何选择合适的策略?

  1. 如果你有明显的“热点数据”(即一小部分数据被频繁访问):推荐使用 allkeys-lru

  2. 如果你希望根据访问频率精准控制(防止偶尔的一次扫表访问导致热点数据被误删):推荐使用 allkeys-lfu

  3. 如果你的 Redis 既做缓存又做数据库(有些重要数据绝对不能丢):推荐使用 volatile-lru,并确保重要数据不设置过期时间。

  4. 如果数据分布非常均匀:使用 allkeys-random

设置方法

CONFIG SET maxmemory-policy allkeys-lru

3. 过期策略:时间到了,数据真的删了吗?

搞懂了“空间不足怎么删”,再来看“时间到了怎么删”。

这是小白最容易误解的一个点。

我们在 Day 1 里学了 EXPIRE key 60。

❌ 误区:很多人以为,60 秒一到,Redis 就会有一个定时器“叮”的一声,立马把这个 Key 销毁。

✅ 真相:如果同一秒钟有 100 万个 Key 同时过期,CPU 光是处理删除操作就卡死了,哪还有空响应客户端的请求?

为了在“省内存”和“不卡顿”之间找平衡,Redis 采用的是 惰性删除 + 定期删除 的组合拳:

① 惰性删除 (Lazy Free) —— “你不来找我,我就不动”

  • 原理:Key 过期了?Redis 根本不主动删它,就这样让它占着内存。

  • 触发:等到下次有客户端来 GET 这个 Key 时,Redis 才会检查一下:“哟,兄弟你过期了?” 然后当场把它删掉,并给客户端返回 nil

  • 优点:极度节省 CPU,不需要额外的线程去监控。

  • 致命缺点:如果这个 Key 过期后再也没人访问(比如一个冷门的验证码),它就会永远占着内存,造成类似内存泄露的效果。

② 定期删除 (Periodic Scan) —— “定期大扫除”

为了解决惰性删除的残留问题,Redis 会搞“随机抽查”。

  • 原理:Redis 会每隔一段时间(默认 100ms),随机抽取一批设置了过期时间的 Key。

  • 检查:检查这批 Key 里谁过期了,过期了就删掉。

  • 循环:如果抽取的这批里过期的比例很高,它会立马再抽一批继续删,直到过期比例下降。

  • 注意:是“随机抽取”,不是“全盘扫描”,否则几千万个 Key 扫一遍,CPU 还是受不了。

为了弥补惰性删除的缺点,Redis 引入了主动检查机制

  • 工作流程: Redis 默认每秒进行 10 次(可配置)内部巡检:

    1. 从设置了过期时间的 Key 字典中随机抽取 20 个 Key。

    2. 删除这 20 个 Key 中已经过期的。

    3. 如果过期的 Key 占比超过了 25%(即超过 5 个),则重复步骤 1,直到过期比例低于 25% 或者达到了最大执行时间限制(通常为 25ms)。

  • 优点:通过限制删除操作的时长和频率,在 CPU 消耗和内存空间之间取得了平衡。

  • 缺点:依然是概率性扫描,无法保证过期的 Key 能够立即被清理。


4. 动手查查:你的 Redis 到底用了多少内存?

光说不练假把式。现在就去命令行里敲一下这个命令,看看你的 Redis 健康状况:

> INFO memory

你会看到一大坨输出,重点关注这几个:

  • used_memory_human: 850.50K <-- 实际存数据用了多少(这是你最该关心的)

  • used_memory_rss_human: 3.50M <-- 操作系统实际分配给了 Redis 多少(包含碎片)

  • maxmemory_human: 0B <-- 最大内存限制(0B 代表不限制,危险!)

实战建议:

如果你的 used_memory 接近了 maxmemory,而且你的应用开始变慢,大概率就是 Redis 正在疯狂进行内存淘汰,这时候 CPU 都在忙着删数据呢。


5. 总结:Redis 的生存哲学

今天的内容比较硬核,我们总结一下**“Redis 内存管理的 3 个真相”**:

  1. 双重防线

    • 过期策略(惰性+定期):负责清理“时间到了”的垃圾。

    • 淘汰策略(LRU/LFU):负责在“空间满了”的时候腾位置。

  2. 生产红线

    • 一定要配置 maxmemory,别让 Redis 把服务器吃挂了。

    • 做缓存推荐用 allkeys-lru,让热数据留下,冷数据滚蛋。

  3. 数据并不是实时的

    • 过期的 Key 可能还在内存里(没被抽查到)。

    • Redis 并不是一个强一致性的数据库,它是在性能和准确性之间做权衡的艺术品。

弄懂了这些,下次面试官问你“Redis 内存满了怎么办”,你就可以自信地从配置讲到算法,再讲到 CPU 调度了。


下期预告

写了三天,我们都是在黑漆漆的命令行里敲代码。但实际开发中,谁会去命令行里存数据呢?我们都是用 Java 代码来操作的。

Java 连接 Redis 有很多种方式:

  • 老牌的 Jedis

  • Spring 默认的 Lettuce

  • 封装好的 RedisTemplate

它们有什么区别?怎么在 Spring Boot 里把 Redis 跑起来?

下一篇 Day 4,咱们正式离开终端,打开 IDEA,写出你的第一行 Redis Java 代码!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐