你有没有遇到过这种情况:Redis 用着用着,内存突然满了,但系统还在拼命往里塞数据?这时候 Redis 并不会崩溃,而是悄悄地“清人腾位”。那它凭什么决定“谁走谁留”?
今天,小米就带你认识 Redis 背后的那位老管家——LRU 算法,用一个仓库的故事,把这套内存回收机制彻底讲清楚。
故事从一个被塞爆的仓库开始
我是小米,今年 31 岁,写代码第 10 年。那天我正坐在工位上,刚泡好一杯咖啡,监控突然红了一片:
Redis used_memory 达到 maxmemory
QPS 开始抖
延迟飙升
我心里一紧,第一反应是: “完了,是不是 Redis 要炸了?”
但很快我发现一个奇怪的现象:Redis 没有挂,而是慢慢恢复了平稳。
我突然意识到一件事:Redis,在自己偷偷“扔东西” 。这就像一个仓库,被塞满了,但仓库管理员并没有锁门不干活,而是开始把“没用的旧货”往外丢。
这个管理员,名字叫 LRU。
Redis 的内存不是无限的
在 Redis 的世界里,有一条铁律:内存是有限的,优先级是残酷的。
你在配置文件里通常会看到这两行:
maxmemory 4gb
maxmemory-policy allkeys-lru
这两行翻译就是:
- Redis 最多只能用 4GB 内存
- 如果内存满了,就按 LRU 算法 来淘汰数据
于是问题来了:什么是 LRU?
LRU 是谁?他是个什么样的人?
LRU,全名是:Least Recently Used(最近最少使用)
我更喜欢把它想象成一个仓库管理员老王。老王的规则只有一句话: “谁最久没被用过,谁先滚蛋。”
是不是很现实?
仓库版 LRU:一眼就懂
想象一个仓库,里面放满了货物(Redis 的 key):
这时候仓库满了,又来了一箱新货 E。老王会怎么做?毫不犹豫,把 D 扔出去。
因为:
- C:刚用过,热乎的
- A:还算常用
- B:有点冷
- D:一天没动过,八成再也用不到了
这,就是 LRU 的直觉逻辑。
Redis 真的用的是“严格 LRU”吗?
看到这里,很多人会下意识点头:“懂了,Redis 用链表维护访问顺序,对吧?”
我当年也是这么想的。但 Redis 官方说了一句非常扎心的话: Redis 使用的是 近似 LRU(Approximate LRU), 不是严格 LRU。
为什么 Redis 不用严格 LRU?
我们先看下严格 LRU要干什么:
- 每个 key 都要记录精确访问时间
- 每次访问都要调整链表顺序
- 淘汰时,直接从尾部删
问题在哪?成本太高。
Redis 是一个:
- 单线程
- 极度追求性能
- 要扛百万 QPS 的系统
如果每次 GET、SET 都去改链表顺序,Redis:我裂开了
Redis 的智慧:近似 LRU + 采样
于是 Redis 选择了一条更“工程化”的路。核心思想只有一句话:
不用找“最久没用的那个”,只要找“一批里面最久没用的那个”就够了。
Redis LRU 的真实算法流程
我们把 Redis 的 LRU 拆开来看:
1、给每个 key 记录一个时间戳
Redis 内部会维护一个字段:
lru: 最近一次访问时间(不是精确时间,是逻辑时钟)
2、内存满了,开始淘汰
Redis 并不会遍历所有 key,而是:
- 随机抽取 N 个 key
- 从中选出 lru 最小的那个
- 把它干掉
这个 N,由一个配置决定:maxmemory-samples 5,意思是:每次随机抽 5 个 key 来“比谁更久没用”
用伪代码看 Redis LRU 的感觉
我给你写一段接近 Redis 思路的伪代码:
注意看重点:
- 没有全量扫描
- 没有排序
- 只比一小撮
这就是 Redis 能跑得飞快的秘密之一。
Redis LRU 的几种策略对比
Redis 并不是只有一种 LRU,而是一大家族。常见策略一览表如下表所示:
小米建议:99% 的业务,直接选 allkeys-lru
LRU 的“性格缺陷”
讲到这里,我必须泼点冷水。LRU 并不是完美的。
缺点 1:短时间热点会“欺骗”它
如果一个 key:
- 曾经被疯狂访问
- 但现在已经废了
它依然可能“苟”很久。
缺点 2:扫描不到全局最优
因为是随机采样:
- 被淘汰的 ≠ 全局最久未使用
- 只是“这一小撮里最久没用的”
但 Redis 的态度是: “够用就行,性能优先。”
Redis 后来为什么又搞了 LFU?
到了 Redis 4.0,事情变了。有人对 Redis 说:“LRU 只看最近,不看频率,不公平。”
于是 Redis 引入了新选手:LFU(Least Frequently Used)
但注意:LRU 仍然是默认王者。
实战建议
结合我这些年踩的坑,总结几条非常实用的经验:
1. 明确你的淘汰策略
maxmemory-policy allkeys-lru
不要用默认不管的状态。
2. 合理设置采样数
maxmemory-samples 10
- 数值越大 → 越接近真实 LRU
- 数值越小 → 性能越好
一般 5~10 是黄金区间。
3. 不要把 Redis 当数据库
Redis 的哲学是: “丢数据是正常的。”
如果你的系统:
- 不能丢
- 丢了就炸
那问题不在 LRU,而在架构。
故事的最后:老王下班了
那天晚上,我盯着监控,看着 Redis 的内存曲线慢慢回落。
我突然觉得:Redis 就像一个经验丰富的老仓库管理员,不追求完美,但永远高效、冷静、务实,而 LRU,就是他最常用的那把扫帚。
END
如果你能看到这里,小米想说一句掏心窝子的话:懂 Redis,不是背命令,而是理解它“为什么这样设计”。
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!
我们,下篇见~