使用 Redis 做缓存时,我们通常不会希望一条缓存永久存在。 例如用户信息可以缓存 30 分钟: 1 2 SET user:1001 ... EXPIRE user:1001 1800 30 分钟以后,这个 Key 就过期了。 但这里会产生一个很有意思的
|
使用 Redis 做缓存时,我们通常不会希望一条缓存永久存在。 例如用户信息可以缓存 30 分钟:
30 分钟以后,这个 Key 就过期了。 但这里会产生一个很有意思的问题:
另外还有一个经常和过期删除混在一起的问题:
这实际上是两套完全不同的机制:
1. 什么是 TTL?TTL 全称是 Time To Live,也就是 Key 还能存活多长时间。 例如:
表示 token:1001 在 60 秒以后过期。 可以使用:
查看剩余时间。 如果返回 45,就表示还剩大约 45 秒。 TTL 还有两个比较特殊的返回值:-1 表示 Key 存在,但是没有设置过期时间;-2 表示 Key 已经不存在。 Redis 还支持直接在 SET 时设置 TTL:
也支持毫秒级的 PEXPIRE、PTTL。 2. Key 到期以后会立刻被删除吗?不一定。 假设:
60 秒过去以后,从逻辑上 code:1001 已经过期,不应该再被用户读取。 但是这不意味着时间刚到第 60 秒的那一瞬间,Redis 一定立刻执行了一次删除操作。 如果 Redis 给每一个 Key 都设置一个定时器:
假设存在几百万个带 TTL 的 Key,就需要维护大量定时任务,成本很高。 因此 Redis 并没有采用“每一个 Key 一个定时器”的方式,而是把过期删除主要分成两种机制:
我们平时也经常把被动过期称为 惰性删除。 3. 什么是惰性删除?假设:
60 秒已经过去。 但是 Redis 可能暂时还没有真正把这个 Key 从内存结构中移除。 这时候客户端执行:
Redis 发现 user:1001 已经过期,于是:
这就是惰性删除。 可以简单理解:
4. 只使用惰性删除行不行?不行。 假设一个 Key:
60 秒以后过期。 但是从此以后再也没有任何客户端访问它。 那么如果只有惰性删除:
大量这样的 Key 积累以后,就会产生严重的内存浪费。 所以 Redis 还需要第二种机制:主动过期。 5. 什么是主动过期?Redis 会周期性检查一部分设置了过期时间的 Key。 可以简单理解:
注意这里不是每隔一段时间扫描 Redis 中所有 Key。 如果 Redis 有一亿个 Key,每次都全量扫描,开销会非常大。 所以 Redis 采用的是:
这样可以在释放过期 Key 和避免 CPU 消耗过大之间取得平衡。 6. 为什么要惰性删除 + 主动过期一起使用?因为两种策略刚好互相补充。 只有惰性删除的问题是:
如果主动过期每次都完整扫描所有 Key,又会导致 CPU 开销太大。 所以 Redis 采用:
既不用给每个 Key 创建单独定时器,也不需要不断全量扫描整个数据库。 7. 过期删除和内存淘汰是一回事吗?不是。 这是这一篇最重要的区别。 假设 Redis 中有:
现在 Redis 内存已经达到上限。 虽然这些 Key 一个都没有真正到期,Redis 仍然可能必须删除一部分数据,为新数据腾出空间。 这时候发生的不是 Expiration,而是 Eviction,也就是内存淘汰。 两者区别可以概括成:
所以一定要记住:
8. 什么是 maxmemory?Redis 可以通过 maxmemory 设置允许用于数据集的最大内存。 例如:
表示 Redis 使用的数据内存达到这个限制以后,需要根据配置决定接下来怎么办。 可以查看:
也可以动态设置:
当 Redis 达到 maxmemory 限制,需要继续写入新数据时,就会根据 maxmemory-policy 决定是否以及如何淘汰 Key。 9. 什么是 maxmemory-policy?maxmemory-policy 决定:
例如:
表示 Redis 达到内存限制以后,从所有 Key 中选择近似最久没有使用的数据进行淘汰。 常见策略主要围绕 LRU、LFU、Random、TTL 等方式展开。 10. 什么是 noeviction?noeviction 的意思很直接:
当 Redis 达到 maxmemory 后,再执行需要增加数据的命令时,会返回错误。 可以理解:
这种策略适合数据不能随便丢失的场景。 如果 Redis 本身就是纯缓存,则通常会考虑允许淘汰旧缓存。 11. 什么是 allkeys-lru?allkeys-lru 可以拆开理解。 allkeys 表示所有 Key 都可能成为淘汰候选。 LRU 是 Least Recently Used,也就是最近最少使用。 例如:
内存满时,Redis 会倾向于淘汰类似 D 这样的 Key。 所以 allkeys-lru 很适合经典缓存场景:
12. Redis 的 LRU 是严格 LRU 吗?不是。 如果想实现完全精确的 LRU,就需要维护严格的访问顺序:
每一次读写都可能需要维护这个结构,会增加额外的内存和 CPU 开销。 Redis 使用的是近似 LRU。 Redis 会对部分 Key 进行采样,再从候选 Key 中选择更符合淘汰条件的对象。 所以不能理解成:
它追求的是:
13. 什么是 allkeys-lfu?LFU 全称是 Least Frequently Used。 与 LRU 的区别在于: LRU 关注最近多久没有访问。 LFU 关注访问频率高不高。 例如:
使用 LRU 时,A 看起来更加“新”。 使用 LFU 时,B 会被认为更加热门。 所以可以简单记:
Redis 的 LFU 还会进行频率衰减,避免一个过去特别热门、现在已经无人访问的 Key 永远保持很高权重。 14. LRU 和 LFU 怎么选?如果业务认为:
可以考虑 LRU。 如果业务中的热点比较稳定,认为:
可以考虑 LFU。 例如某条新闻上午突然爆火、下午已经无人访问,如果只记录历史访问总次数,它可能一直显得很热门。 所以 Redis 的 LFU 会随着时间对访问频率进行衰减。 15. 什么是 allkeys-random?allkeys-random 就是从所有 Key 中随机淘汰。 例如:
Redis 可能随机选择 C 删除。 它不关心这个 Key 最近有没有访问,也不关心它的访问频率。 算法简单,但如果缓存存在明显冷热差异,通常没有 LRU/LFU 那么有针对性。 16. 什么是 volatile?前面讲的是 allkeys-lru、allkeys-lfu、allkeys-random。 其中 allkeys 表示:
而 volatile-* 只会从设置了 TTL 的 Key 中进行选择。 例如:
如果使用 volatile-lru,Redis 只会在 A 和 B 中按照 LRU 思路选择淘汰对象。 C 和 D 不参与。 所以可以直接记:
17. volatile-lru、volatile-lfu、volatile-random这三个其实很好理解。 volatile-lru:只在带 TTL 的 Key 中淘汰最近最少使用的。 volatile-lfu:只在带 TTL 的 Key 中淘汰访问频率较低的。 volatile-random:只在带 TTL 的 Key 中随机淘汰。 所以真正需要理解的是:
18. 什么是 volatile-ttl?volatile-ttl 同样只考虑设置了过期时间的 Key。 但是它不看 LRU,也不看访问频率,而是优先选择剩余 TTL 更短的 Key。 例如:
A 本来就快过期,因此更适合作为优先淘汰对象。 19. Redis 到底有多少种淘汰策略?很多老的 Redis 面试资料会总结成经典的 8 种:
但是这个说法和 Redis 版本有关。 较新的 Redis 又增加了 LRM(Least Recently Modified),例如 allkeys-lrm 和 volatile-lrm。 LRM 关注的是 Key 最近一次修改时间,而不是最近一次访问时间。 所以学习时不要把:
当成绝对结论。 更重要的是理解这些策略的分类方式。 20. volatile 系列有一个坑假设 Redis 使用:
但是数据库中的所有 Key 都没有设置 TTL。 那么 volatile-lru 根本找不到任何候选对象。 最终效果会类似 noeviction:
所以使用 volatile-* 的前提是:
21. 缓存场景应该怎么选?如果 Redis 完全用于缓存,数据丢失后可以从数据库重新构建,那么通常可以考虑 allkeys-lru 或 allkeys-lfu。 如果 Redis 中同时存在:
可以利用 TTL 与 volatile-* 进行一定程度的区分。 不过从架构设计上来说,缓存数据和重要数据如果淘汰要求完全不同,往往更适合拆分到不同 Redis 实例中。 22. TTL 到期和 LRU 淘汰有什么本质区别?假设:
这个 Key 还有 30 分钟才过期。 但是 Redis 内存满了,并且使用 allkeys-lru。 如果这个 Key 很久没有访问,那么:
所以:
因此设置 1 小时 TTL,并不代表这个 Key 一定会存在整整 1 小时。 23. Key 过期了为什么内存没有马上下降?Key 从逻辑上已经到期,不代表那一瞬间一定已经完成实际删除。 如果这个 Key 没有再次被访问,惰性删除就不会触发。 主动过期又采用抽样和渐进处理,并不是到某个时间点突然扫描所有 Key。 所以短时间内可能出现:
随着主动过期继续执行,或者这些 Key 再次被访问,它们会逐渐被删除。 24. 为什么大量 Key 同时过期可能有问题?假设业务一次写入 100 万个缓存,并且全部设置: TTL = 30 分钟 那么 30 分钟以后,大量 Key 会在非常接近的时间同时过期。 Redis 自身需要短时间处理大量过期数据。 同时业务发现缓存不存在,又可能大量请求数据库。 这就可能进一步引发后面要学习的 缓存雪崩。 因此大量同类缓存通常不会把 TTL 设置得完全一样,而是加入一定随机值。 例如:
把过期时间适当打散。 25. EXPIRE 和淘汰策略要怎么区分?例如面试中可能问:
不是。 EXPIRE 属于 TTL 过期机制,主要通过惰性删除和主动过期进行处理。 LRU、LFU 则属于内存淘汰机制,主要在 Redis 达到 maxmemory、需要释放内存时发挥作用。 所以:
两者触发条件完全不同。 26. 整个流程假设:
这个 Key 之后可能经历:
所以一个 Key 消失,并不一定意味着 TTL 到期。 也可能是:
27. 最容易搞错的几个地方第一,Key 过期并不代表到期瞬间一定完成删除,Redis 会通过惰性删除和主动过期配合清理。 第二,过期删除和内存淘汰不是一回事。前者由 TTL 触发,后者由 maxmemory 压力触发。 第三,设置了 TTL 的 Key 也可能在 TTL 到期以前就因为内存不足被淘汰。 第四,Redis 的 LRU 和 LFU 都是近似实现,不是为了追求绝对精确而维护高成本的全局结构。 第五,volatile-* 只会从带 TTL 的 Key 中选择;如果没有满足条件的 Key,就可能无法通过淘汰继续释放空间。 第六,“Redis 有 8 种淘汰策略”是经典版本中的常见总结,具体策略数量需要结合 Redis 版本来看。 |
2021-06-02
2021-06-05
2022-06-27
2024-07-31
2024-02-19