Redis篇4——Redis深度剖析:内存淘汰策略与缓存的三大“天坑”

在前面的文章中,我们聊了分布式锁、聊了持久化,这些都是在讲"怎么用好 Redis"。

但今天我们要聊一个更底层、更残酷的话题:资源限制与系统脆弱性

Redis 再快,它也是基于内存的。内存是昂贵的资源,不可能无限扩容。同时,Redis 作为数据库的挡箭牌,一旦它出了问题,数据库瞬间就会面临灭顶之灾。

今天这篇,我们就来把 Redis 的 内存淘汰策略缓存击穿/穿透/雪崩 彻底讲透。


一、 内存满了怎么办?(内存淘汰策略)

Redis 的性能虽然强悍,但它不是黑洞。在生产环境中,我们通常会在配置文件中设置 maxmemory 来限制 Redis 的最大内存使用量。

那么问题来了:当数据量存满了,新数据又要塞进来,Redis 该扔掉谁?

Redis 提供了 8 种策略,看起来眼花缭乱。其实作为博主,我建议你不要死记硬背,而是从 "范围""算法" 两个维度去理解。

1. 维度一:你要对谁下手?(筛选范围)

Redis 并不总是对所有数据都"赶尽杀绝",它把数据分为了两类圈子:

  • 圈子一:Volatile(临时工区 - 仅针对设置了 TTL 的 Key)

    • 含义 :Redis 只会从设置了过期时间的 Key 中进行淘汰。

    • 场景 :这非常适合 "混合存储" 场景。比如你的 Redis 里既存了必须持久化的重要配置(没设 TTL),又存了大量的临时缓存(设了 TTL)。当内存不足时,Redis 会很懂事地只清理那些本来就会过期的缓存,绝对不会碰你的重要数据。

  • 圈子二:Allkeys(全员区 - 针对所有 Key)

    • 含义 :不管你设没设过期时间,一视同仁,大家都在被淘汰的范围内。

    • 场景 :这是 "纯缓存" 模式的最佳选择。因为作为缓存,只要内存满了,没用的数据就该腾位置,不需要保留任何特权阶级。

2. 维度二:你要怎么挑?(筛选算法)

  • LRU (Least Recently Used)淘汰最久没被使用过的

    • 逻辑:你虽然进来的早,但最近 3 个月都没人查过你,留着浪费资源,请你走人。
  • LFU (Least Frequently Used)淘汰使用频率最低的

    • 逻辑:虽然你刚才被访问了一次,但你是这一年来唯一的访问,说明你不够热,请你走人。
  • Random随机。闭着眼瞎选一个删掉。

  • TTL剩余寿命最短。既然你马上就要过期了,不如我提前送你一程(仅适用于 Volatile 范围)。

3. 策略大盘点与选型建议

将上述两个维度组合,再加上默认策略,就构成了 8 种策略:

策略名 筛选范围 淘汰规则 博主推荐场景
noeviction - 不删除,直接报错 (默认配置) 适用于数据绝不能丢的场景,需配合完善的扩容监控。
volatile-lru 有TTL的 最久未用 适用于 Redis 既做缓存又做存储 的混合场景。
volatile-lfu 有TTL的 最少频率 同上。
volatile-random 有TTL的 随机删除 极其少用。
volatile-ttl 有TTL的 即将过期 哪怕没过期,只要快到了就删,不如 LRU 智能。
allkeys-lru 所有Key 最久未用 最常用! 适用于通用的缓存场景,保留热点数据。
allkeys-lfu 所有Key 最少频率 适用于有些生僻数据很久才被访问一次的场景。
allkeys-random 所有Key 随机删除 极其少用。

说明:

  • 绝大多数业务场景,直接闭眼选 allkeys-lru

  • 因为缓存的本质就是"把热的数据留下",LRU 是最符合这个直觉的算法。


二、 缓存的三大"天坑":击穿、穿透、雪崩

Redis 就像一道堤坝,挡在脆弱的 MySQL 数据库前面。如果这道堤坝出了问题,洪水(高并发请求)就会直接冲垮数据库。

我们按照问题的**"刁钻程度"**,由点到面来分析。

1. 缓存击穿 (Cache Breakdown) ------ "单点爆破"

简单来说就是热点key过期的瞬间,大量请求达到了DB。

这是最常见,但也最需要技术含量的场景。

  • 现象:

    某个 超级热点 Key(比如微博热搜、秒杀商品),并发量极高。在它 TTL 过期的一瞬间,几万个请求同时发现 Redis 没数据,同时冲向数据库去构建缓存。

    结果: 数据库瞬间被打死。

  • 解决方案:

    针对这个问题,我们有两种截然不同的思路。

    方案 A:互斥锁 (Consistency First)

    既然大家都要去查数据库,那就排队!

    • 思路:只允许一个线程去查询数据库并重建缓存,其他线程阻塞等待。

    • 流程

      1. 线程 1 发现缓存没数据。

      2. 线程 1 获取分布式锁(Mutex Lock)。

      3. 线程 1 去查库、写缓存、释放锁。

      4. 线程 2、3、4 在获取锁失败后,阻塞等待,或者休眠一会再重试(Double Check)。

    • 优点:数据一致性高。

    • 缺点:性能有损耗,大家都在等那一个人。

    方案 B:永不过期 + 逻辑过期 (Availability First)(就好像他本应该过期的,但是我留下了他用来保护数据库,反正我先返回给你,然后更新重置缓存)

    这个方案牺牲了一点点数据一致性,换取了极致的性能。这也是很多高并发系统的首选。

    • 核心思路

      • Redis 层面 :Key 不设置 TTL,所以物理上它永远不会过期(除非被淘汰)。

      • 业务层面 :我们在 Value 的内容里,多存一个属性:expireTime(逻辑过期时间)。

    • 具体实现流程

      1. 查询缓存:请求命中缓存后,读取 Value。

      2. 判断过期 :检查 Value 里的 expireTime 字段,看是否已过期。

      3. 未过期:直接返回数据,结束。

      4. 已过期

        • 获取锁:尝试获取互斥锁。

        • 开启异步线程 :如果获取锁成功,说明我是"被选中"的那个人。我立马开启一个独立的异步线程 去查询数据库、更新缓存(重置 expireTime)、释放锁。

        • 返回旧值 :无论是获取锁成功的当前线程,还是获取锁失败的其他线程,都不用等,直接把手里这份"已经过期"的旧数据返回给用户。

    • 说明:

      这种方案非常巧妙。它实际上是"悄悄地在后台更新"。用户在更新完成前的几百毫秒内,虽然看到的是旧数据,但系统响应速度极快,没有任何阻塞。

2. 缓存穿透 (Cache Penetration) ------ "打在了空气上"

关键在⼀个透字,透过去了,即数据在 Redis 缓存和数据库中都不存在这样缓存永远 不会生效,请求都会打到数据库。(说白了,没得给你缓存的机会)

如果说击穿是"热点失效",那穿透就是"恶意攻击"。

  • 现象:

    请求想要的数据,Redis 里没有,数据库里也没有。

    这样 Redis 就成了摆设,请求每次都直通数据库。这通常是代码 bug 或者黑客恶意攻击(一直请求 ID=-1 的数据)。

  • 解决方案

    1. 缓存空对象 :数据库查不到,我也在 Redis 存一个 null,并设个短一点的过期时间。下次你再来查 -1,Redis 直接告诉你"是空",别烦数据库。

    2. 布隆过滤器 (Bloom Filter):在访问 Redis 之前加一道屏障。布隆过滤器能高效地判断"这个 ID 是否存在"。如果布隆说不存在,直接打回,连 Redis 都不用查。

布隆过滤器原理分析:

如果说缓存空值是被动防御,那布隆过滤器就是主动拦截。

  • 核心原理 : 底层是一个巨大的 BitMap (位图) + N 个哈希函数

    • 存入时 :经过 N 次 Hash 计算,将对应位置的比特位全部置为 1

    • 查询时:同样的 N 次 Hash 计算,去查看对应位置。

  • 判定规则(铁律)

    • 只要有一位是 0 :说明该 Key 绝对不存在 。 -> 直接拦截,无需查库

    • 如果全都是 1 :说明该 Key 可能存在 (存在极低概率是别的 Key 凑出来的 Hash 冲突)。 -> 放行

3. 缓存雪崩 (Cache Avalanche) ------ "全线崩塌"

同⼀时间⼤量key过期,导致查询全打到数据库上

最后一种情况,是规模最大的灾难。

  • 现象:

    大量的 Key 在同一时间集体过期,或者 Redis 节点直接宕机。

    这导致此刻所有的请求全部涌向数据库,数据库瞬间压力过大而崩溃。

  • 解决方案

    1. 打散过期时间 :在设置 TTL 时,不要设成固定的 1 小时,而是 TTL + 随机数(1-5分钟)。让 Key 排队过期,不要挤在一起。

    2. 高可用架构:利用 Redis Sentinel 或 Cluster,保证 Redis 挂了一个节点,其他的能立马顶上。

    3. 服务降级:当缓存和数据库都扛不住时,直接返回默认值或错误提示,保住系统不崩。


总结

Redis 的学习,不能只停留在命令的使用上。

  • 内存淘汰 是为了让 Redis 永远只留最有价值的数据。

  • 击穿/穿透/雪崩 的防御,是为了让 Redis 在面对流量洪峰和异常情况时,依然能稳稳地守护身后的数据库。

掌握了这些,你才算真正具备了驾驭大规模 Redis 集群的能力。下一篇,我们将深入 Redis 的 集群架构,敬请期待!

相关推荐
laocooon5238578865 小时前
C#二次开发中简单块的定义与应用
android·数据库·c#
hans汉斯6 小时前
【软件工程与应用】平移置换搬迁系统设计与实现
数据库·人工智能·系统架构·软件工程·汉斯出版社·软件工程与应用
gugugu.6 小时前
Redis List类型完全指南:从原理到实战应用
数据库·redis·list
Hello.Reader6 小时前
Flink SQL ALTER 语句在线演进 Table/View/Function/Catalog/Model
数据库·sql·flink
白学还是没白学?6 小时前
exec db docker from A to B
数据库·docker·容器
云老大TG:@yunlaoda3606 小时前
腾讯云国际站代理商TCCC的技术适配服务包括哪些内容?
数据库·云计算·腾讯云
元气满满-樱6 小时前
MySql部署多实例
数据库·mysql·adb
LFly_ice6 小时前
PostgreSql 常用聚合函数
数据库·postgresql