Redis 为什么宁愿“删数据”,也不愿慢下来?答案在 LRU

25 阅读5分钟



你有没有遇到过这种情况: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要干什么:

  1. 每个 key 都要记录精确访问时间
  2. 每次访问都要调整链表顺序
  3. 淘汰时,直接从尾部删

问题在哪?成本太高。

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岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!

我们,下篇见~