Redis的持久化

结合你提供的详细资料,我会从"核心概念→配置细节→操作逻辑→优缺点对比"四个维度,把Redis持久化(RDB+AOF)拆解得更清晰,同时结合你提到的关键配置和实际场景,帮你彻底理解如何用、怎么选。

先明确一个前提:为什么需要持久化?

Redis是内存数据库 ------数据默认只存在内存里。如果服务器断电、进程崩溃,内存里的数据会直接消失。持久化的本质就是:把内存里的数据/操作,"备份"到硬盘上,让重启后能恢复数据。

10.1 RDB方式:给数据拍"定时快照"

RDB的核心是"记录某一时刻的所有数据结果 ",就像给当前内存里的所有key-value拍一张"全家福",存成.rdb文件到硬盘。

一、RDB的关键配置(你资料里提到的重点)

这些配置决定了RDB文件怎么存、存多久、是否安全,直接影响生产环境使用:

|-----------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------------|
| 配置项 | 作用 | 通俗理解 & 生产建议 |
| dbfilename dump.rdb | 定义RDB快照文件名 | 建议改成dump-端口号.rdb(比如dump-6379.rdb),避免多Redis实例用同一个文件冲突 |
| dir | 定义RDB文件的存储路径 | 选一个存储空间大的目录(比如/data/redis),别存在系统盘,防止磁盘满了影响服务器 |
| rdbcompression yes | 是否用LZF算法压缩RDB文件 | - 开(yes):文件小、省磁盘,但会消耗一点CPU; - 关(no):省CPU,但文件会"巨大"(比如10G数据可能变20G),生产默认开 |
| rdbchecksum yes | 是否用CRC64算法校验RDB文件 | - 开(yes):能发现文件损坏(比如磁盘出错),但读写时多10%时间; - 关(no):快一点,但数据坏了可能不知道,生产默认开 |
| save second changes | 自动触发RDB的条件(核心) | 比如save 3600 1=3600秒(1小时)内有1次key修改,就拍快照; 注意 :别设成"包含关系"(比如同时设save 60 100save 30 50),会重复触发;频率要平衡------太频繁占CPU,太稀疏丢数据多 |

二、RDB的触发方式:手动/自动

1. 手动触发(两种命令,选谁?)

|----------|-------------------|-------------------------------------------------------|----------------------------|
| 命令 | 作用 | 优缺点 | 生产建议 |
| save | 直接在主线程执行RDB,直到完成 | 优点:简单; 缺点:阻塞Redis(期间不能处理任何请求,数据多的话可能卡几秒/几分钟) | 绝对不用!线上用会导致服务不可用 |
| bgsave | 后台异步执行RDB(主线程不阻塞) | 优点:主线程能正常处理请求; 缺点:需要"fork子进程"(复制一份内存数据),数据量大时fork会耗资源 | 生产手动触发全用它(比如备份时执行bgsave) |

关键:fork子进程是什么?

你资料里提到的fork是RDB的核心机制------Redis会复制一个和自己一模一样的子进程,让子进程去写RDB文件,主线程继续干活。

这里有个"坑":fork时会"克隆"内存数据,虽然用了"写时拷贝"(只有修改过的数据才复制),但如果内存里有10G数据,fork瞬间还是会占一定资源,可能短暂影响性能。

2. 自动触发

就是靠上面的save second changes配置,比如默认配置里的:

复制代码
save 900 1    # 900秒内改1次key,触发bgsave
save 300 10   # 300秒内改10次key,触发bgsave
save 60 10000 # 60秒内改10000次key,触发bgsave

只要满足任意一个条件,Redis就会自动执行bgsave拍快照。

三、RDB的优缺点(结合你资料总结)

|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| 优点 | 缺点 |
| 1. 文件小(二进制压缩),备份方便(比如每天凌晨用RDB做全量备份); 2. 恢复快(直接把整个文件读进内存,不用执行命令); 3. 存储效率高 | 1. 丢数据风险高 :如果两次快照之间宕机(比如设了1小时拍一次,59分时宕机),这59分钟的数据全丢; 2. 数据量大时,fork子进程和写文件会耗CPU/内存,影响服务性能; 3. 不同Redis版本的RDB文件可能不兼容(比如6.x的RDB不能直接给5.x用) |

10.2 AOF方式:记"操作日志",实时性更强

AOF的核心是"记录每一次写操作的过程 "------不是存数据结果,而是存"你做了什么修改"(比如set key1 123lpush list1 a),重启时再重新执行这些命令,恢复数据。

它解决了RDB"丢数据多"的问题,现在是Redis持久化的主流。

一、AOF的执行流程(四步走)

  1. 写缓冲 :客户端的写命令(如set)先存到"AOF缓冲区"(内存里,快);
  2. 同步到磁盘 :根据配置的"同步策略",把缓冲区的命令写到硬盘的.aof文件;
  3. 文件重写 :AOF文件会越写越大(比如反复set key1 x),Redis会"压缩"文件(只留最终有效命令);
  4. 重启恢复 :Redis重启时,读.aof文件,重新执行所有命令,恢复数据。

二、AOF的关键配置(你资料里的重点)

1. 基础开关&文件名

|---------------------------------|---------------|----------------------------------------------------------|
| 配置项 | 作用 | 生产建议 |
| appendonly yes | 是否开启AOF(默认no) | 必须开!除非你能接受丢数据 |
| appendfilename appendonly.aof | AOF文件名 | 建议改成appendonly-端口号.aof(比如appendonly-6379.aof),和RDB区分 |
| dir | AOF文件存储路径 | 和RDB用同一个路径(比如/data/redis),方便管理 |

2. 核心:AOF同步策略(appendfsync)------决定"丢多少数据"

这是AOF最关键的配置,平衡"数据安全"和"性能":

|------------|---------------------------|--------------|------------------------|-----------------------|
| 策略 | 逻辑 | 数据安全性 | 性能 | 生产建议 |
| always | 每执行一次写命令,就立即把命令同步到硬盘 | 最高(零丢失) | 最差(每次写都要等磁盘IO,QPS会降很多) | 除非是金融级场景(比如交易数据),否则不用 |
| everysec | 每秒同步一次缓冲区的命令到硬盘 | 较高(最多丢1秒数据) | 较好(每秒只等一次IO,对性能影响小) | 99%的场景选这个,兼顾安全和性能 |
| no | 让操作系统自己决定什么时候同步(通常是30秒左右) | 最低(可能丢几十秒数据) | 最好(Redis不管IO,全交给系统) | 不建议,数据丢太多,风险高 |

3. 解决AOF文件过大:重写配置

你资料里提到AOF会"越写越大"(比如反复set key1 1set key1 2set key1 3,AOF会存3条命令),所以需要"重写"------把无效命令删掉,合并重复命令,压缩文件体积。

|-----------------------------------|---------------|----------------------------------|
| 配置项 | 作用 | 通俗理解 |
| auto-aof-rewrite-min-size 64mb | 触发自动重写的最小文件大小 | AOF文件至少要到64MB才会考虑重写(太小没必要) |
| auto-aof-rewrite-percentage 100 | 触发自动重写的比例 | AOF文件大小是"上次重写后大小"的2倍(100%)时,触发重写 |

例子:上次重写后AOF是64MB,当文件涨到128MB(64MB×2),Redis会自动执行重写,把128MB的文件压缩成几十MB。

4. 重写的两种方式

|------|----------------------------|-------------------------------------|
| 方式 | 命令 | 逻辑 |
| 手动重写 | bgrewriteaof | 后台异步执行(和bgsave类似,不阻塞主线程),想压缩时手动执行 |
| 自动重写 | 靠上面的auto-aof-rewrite-*配置 | 满足"文件大小≥64MB"且"是上次重写后2倍",自动触发 |

重写规则(很重要,决定压缩效果)

  • 过期的数据不写(比如set key1 123后key1过期了,重写时就不记这条命令);
  • 无效命令删掉(比如set key1 1del key1,这两条命令抵消,重写时全删);
  • 重复命令合并(比如lpush list1 alpush list1 b,合并成lpush list1 a b);
  • 集合类命令最多64个元素(比如lpush list1 1-100,会拆成lpush list1 1-64lpush list1 65-100,避免命令太长撑爆客户端缓冲区)。

三、AOF的优缺点(结合你资料总结)

|----------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
| 优点 | 缺点 |
| 1. 数据安全高(everysec策略最多丢1秒数据); 2. 日志是明文命令(比如.aof里能看到set key1 123),能手动修改(比如误删key,可编辑AOF删掉del命令); 3. 重写机制能控制文件大小 | 1. 文件比RDB大(即使重写后,也比同数据量的RDB大); 2. 恢复慢(需要重新执行所有命令,10G的AOF可能要恢复几分钟); 3. 同步策略如果设always,会严重影响性能 |

10.3 终极对比:RDB vs AOF(你资料的核心总结)

直接用表格清晰对比,帮你做选择:

|-------|---------------------------|--------------------------------|
| 对比维度 | RDB | AOF |
| 核心逻辑 | 存"数据快照"(全家福) | 存"操作日志"(记账本) |
| 存储空间 | 小(二进制压缩) | 大(明文命令,重写后会缩小) |
| 存储速度 | 慢(每次要写全量数据,fork耗资源) | 快(只追加命令,IO压力小) |
| 恢复速度 | 快(直接读文件进内存) | 慢(要执行所有命令) |
| 数据安全性 | 低(可能丢两次快照间的所有数据) | 高(everysec最多丢1秒,always零丢失) |
| 资源消耗 | 高(fork子进程+写全量数据,耗CPU/内存) | 低(只追加命令,重写也是后台执行) |
| 启动优先级 | 低(如果RDB和AOF都开,重启时优先加载AOF) | 高(优先加载,数据更新) |

10.4 生产环境怎么选?(官方建议+实战经验)

  1. 优先选"RDB+AOF同时开"
    • 重启时优先加载AOF(数据最新,丢得少);
    • RDB用来做全量备份(比如每天凌晨用bgsave生成RDB,传到备份服务器,万一AOF文件损坏,还能靠RDB恢复)。
  1. 只选RDB的场景
    • 对数据不敏感(比如缓存热点数据,丢了能从数据库重新加载);
    • 追求极致的恢复速度(比如Redis作为缓存,重启要快)。
  1. 不建议只选AOF
    • 官方提到"可能出现Bug"(虽然少见,但AOF日志如果损坏,恢复可能失败;而RDB更稳定);
    • 恢复速度慢,大文件恢复耗时久。
  1. 都不选的场景
    • 纯内存缓存(比如存会话数据,丢了不影响业务,只需要Redis快)。

最后补充:你资料里的其他配置(非持久化,但很重要)

这些配置虽然不是持久化核心,但影响Redis性能和稳定性,顺便解释一下:

  • zset-max-ziplist-value 64:zset类型的value如果≤64字节,用紧凑的ziplist结构(省内存);超过就用普通zset(查得快)。
  • hll-sparse-max-bytes 3000:HyperLogLog类型(统计基数用),value≤3000字节用稀疏结构(省内存),超过用稠密结构(计算快)。
  • activerehashing yes:Redis会每100毫秒用1毫秒CPU,对哈希表重新哈希(省内存);如果你的业务对延迟要求极高(比如不能接受2毫秒延迟),就设no,否则默认yes
  • client-output-buffer-limit:限制客户端的输出缓冲区(比如slave同步数据时,如果太慢,缓冲区满了就断开连接,避免Redis内存被撑爆)。

Redis的删除策略

结合你提供的详细资料,我会围绕"平衡内存与CPU资源"这个核心目标,详细解析Redis的两种删除策略(基于过期时间和基于内存淘汰),包括它们的工作原理、优缺点缺点和实际应用。

一、数据删除策略的核心目标

Redis的删除策略本质上是在**"内存占用"和"CPU消耗"之间找平衡**:

  • 若过度追求"节省内存",可能会频繁删除数据,消耗大量CPU,导致Redis响应变慢;
  • 若过度追求"节省CPU",可能会让大量无效数据堆积在内存,导致内存溢出(OOM)。

两种策略(过期时间删除+内存淘汰)相互配合,就是为了避免"顾此失彼",保证Redis整体性能稳定。

二、基于过期时间的删除策略

当我们用EXPIRE等命令给key设置过期时间后,Redis需要决定何时删除这些过期的key 。首先明确一个重要结论:"过期的数据不会立即被物理删除",而是通过以下三种策略处理:

1. 定时删除:"到期就删,绝不拖延"

  • 原理:给每个设置了过期时间的key绑定一个"定时器",当过期时间一到,定时器就立即执行删除操作。
  • 优点:内存友好------过期数据会被及时清理,不会占用内存。
  • 缺点:CPU压力大------无论当前Redis有多忙,定时器都会强制占用CPU执行删除,可能导致正常请求响应变慢(比如大量key同时过期时,CPU会被删除操作占满)。
  • 总结 :这是"用CPU换内存"的策略,Redis不采用这种方式,因为会严重影响性能。

2. 惰性删除:"用到了才检查,没用到就不管"

  • 原理 :数据过期后不主动删除,等到下次访问该key时才做检查:
    • 如果没过期,正常返回数据;
    • 如果已过期,立即删除并返回"不存在"。
  • 优点:CPU友好------只在必要时(访问数据时)才检查过期,平时不消耗CPU。
  • 缺点:内存不友好------如果过期数据长期不被访问,会一直占用内存(比如一个key过期后再也没人查,就会成为"内存垃圾"),可能导致内存泄漏。
  • 总结:这是"用内存换CPU"的策略,Redis会用到这种方式,但仅靠它不够(无法解决内存垃圾堆积问题)。

3. 定期删除:"周期性抽查,重点清理"

这是Redis实际使用的核心策略,结合了定时删除和惰性删除的优点,平衡CPU和内存消耗。

工作流程(根据你资料中的细节):
  1. 执行频率 :Redis启动时读取server.hz配置(默认10),每秒钟执行server.hz次检查(即默认每100ms一次)。
  2. 每次执行时长 :每次检查最多占用250ms / server.hz(默认25ms),避免长时间占用CPU。
  3. 检查逻辑
    • 从所有数据库(0-15号库)中,随机挑选W个设置了过期时间的key(W是固定值,由ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP定义)。
    • 删除其中已过期的key。
    • 如果本轮删除的过期key占比超过25%(说明过期key较多),就重复检查当前数据库;如果占比≤25%,就检查下一个数据库。
    • 记录当前检查到哪个数据库(current_db),下次从这里继续,避免每次都从0号库开始。
  • 优点
    • CPU消耗可控------每次检查有时间上限(25ms),不会阻塞服务;
    • 内存占用可控------通过"随机抽查+重点清理"(占比超25%就重复检查),能持续清理过期数据,避免大量堆积。
  • 总结:这是"周期性抽查+动态调整频率"的策略,是Redis处理过期数据的主力方式。

三种过期删除策略的配合

Redis实际采用"定期删除+惰性删除"的组合:

  • 定期删除:主动抽查并清理部分过期key,减少内存垃圾;
  • 惰性删除:当访问某个key时,兜底检查是否过期,确保返回有效数据。

这种组合既避免了定时删除的CPU浪费,又缓解了惰性删除的内存堆积问题。

三、基于内存淘汰的删除策略(逐出算法)

当Redis内存达到maxmemory限制(比如配置了最大使用4GB内存),且有新数据要写入时,Redis会触发内存淘汰策略,删除部分数据腾出空间。

触发条件

  1. 内存使用量达到maxmemory配置的值(默认不限制,生产环境必须设置,比如物理内存的50%-70%);
  2. 执行新的写命令时,内存不足。

影响淘汰的关键配置

|---------------------|----------------------------|---------------------|
| 配置项 | 作用 | 重要性 |
| maxmemory | 设置Redis最大可用内存(如4gb) | 必须配置,否则可能耗尽服务器内存 |
| maxmemory-samples | 每次随机挑选多少个key作为"待淘汰候选"(默认5) | 选太少可能淘汰不准确,选太多消耗CPU |
| maxmemory-policy | 淘汰策略(核心) | 决定优先删哪些数据,直接影响业务 |

8种内存淘汰策略(按淘汰范围分类)

根据你资料中的分类,策略分为三类:

1. 只淘汰"设置了过期时间的key"(4种)
  • volatile-lru:淘汰"最近最少使用"的过期key(关注"访问时间");
  • volatile-lfu:淘汰"最近使用次数最少"的过期key(关注"访问频率");
  • volatile-ttl:淘汰"剩余过期时间最短"的过期key(即将过期的先删);
  • volatile-random:随机淘汰一个过期key。

适用场景:需要保留永久有效数据(如配置信息),只清理临时过期数据(如会话)。

2. 淘汰"所有key"(不管是否过期,3种)
  • allkeys-lru:淘汰"最近最少使用"的所有key(最常用);
  • allkeys-lfu:淘汰"最近使用次数最少"的所有key;
  • allkeys-random:随机淘汰任意key。

适用场景:Redis作为缓存(如热点数据缓存),所有数据都是临时的,优先保留常用数据。

3. 不淘汰,直接报错(1种)
  • noeviction:禁止淘汰任何数据,新写操作直接返回OOM错误((error) OOM command not allowed...)。

适用场景 :几乎不用,除非数据绝对不能丢(但此时应关闭过期时间,且maxmemory设为足够大)。

淘汰执行流程

  1. 每次写命令前,Redis调用freeMemoryIfNeeded()检查内存是否充足;
  2. 若内存不足,根据maxmemory-policy从候选key中挑选要删除的数据;
  3. 重复删除,直到内存足够或所有候选key都删完;
  4. 若仍不足,返回OOM错误。

四、总结:两种策略的关系与选择

|-----------|-----------------|--------------------------|------------------------------------------------------------------------------------------------------|
| 策略类型 | 解决的问题 | 核心逻辑 | 生产建议 |
| 基于过期时间的删除 | 清理"已过期但未删除"的key | 定期删除(主动抽查)+ 惰性删除(访问时检查) | 无需手动干预,依赖Redis默认机制即可 |
| 基于内存淘汰的删除 | 内存不足时腾出空间 | 按策略淘汰部分key(LRU/LFU/TTL等) | 必须配置maxmemorymaxmemory-policy: - 缓存场景用allkeys-lru; - 有永久数据场景用volatile-lru; - 禁用noeviction |

这两种策略相辅相成:过期删除策略负责日常清理过期数据,内存淘汰策略在内存紧张时"兜底",共同保证Redis在内存和CPU之间的平衡,避免性能问题或数据丢失。

要理解缓存的这4个核心问题(预热、雪崩、击穿、穿透),我们可以结合"生活场景+技术原理+解决方案"的方式,用最通俗的语言拆解,每个概念都先讲"是什么""举例子",再讲"怎么解决"。

一、缓存预热:"提前把货备到货架上"

1. 通俗理解

缓存的核心是"把数据库里的热点数据,提前加载到Redis等缓存中",这样用户请求时直接查缓存,不用查数据库。

缓存预热就是:在系统启动/流量高峰前,主动把"未来会被频繁访问的数据"加载到缓存里,避免用户第一次访问时"缓存没数据,只能查数据库"(也就是"缓存冷启动"问题)。

2. 举个生活例子

你开了一家便利店:

  • 平时用户买可乐,你需要从仓库(数据库)拿出来放到货架(缓存),再给用户(第一次访问慢);
  • 如果你知道周末会有很多人买可乐(流量高峰),提前在周五晚上就把100瓶可乐摆到货架上(缓存预热),周末用户来直接拿,不用等你去仓库取------这就是缓存预热的作用。

3. 技术场景举例

比如电商平台的"双11活动":

  • 活动开始后,"iPhone 15""华为Mate 60"这些商品的详情页会被千万用户访问;
  • 如果不做缓存预热,双11 0点第一个用户访问时,缓存里没有数据,会直接查数据库(数据库压力骤增,可能卡崩);
  • 做了缓存预热:活动前1小时,通过脚本主动把这些热门商品的信息(价格、库存、详情)从数据库查出来,写入Redis缓存;
  • 0点后用户访问,直接读Redis,速度快,数据库也没压力。

4. 怎么实现缓存预热?

  • 脚本批量加载:写个Python/Shell脚本,查询数据库的热点数据(比如按历史访问量排序的Top1000商品),批量写入缓存;
  • 接口触发:系统启动后,调用一个"预热接口",主动加载数据;
  • 增量预热:如果数据太多,分批次加载(比如每次加载100条,避免一次性给数据库和缓存太大压力)。

二、缓存雪崩:"货架全塌了,所有用户都挤去仓库"

1. 通俗理解

缓存里的大量数据同时过期 ,或者缓存服务器(比如Redis集群)突然宕机,导致所有用户的请求都"绕开缓存,直接冲击数据库"------数据库扛不住这么大的流量,直接崩了,这就是缓存雪崩。

2. 举个生活例子

还是你的便利店:

  • 你之前把所有饮料(可乐、雪碧、矿泉水)都摆到货架上,并且规定"所有饮料晚上8点过期,必须下架";
  • 晚上8点一到,货架上所有饮料都没了(缓存大量过期);
  • 刚好这时来了100个顾客买饮料,你只能让所有人等着,自己跑回仓库一瓶瓶拿(所有请求查数据库);
  • 仓库门口挤成一团,你根本忙不过来,最后顾客全走了(数据库崩了,服务不可用)。

3. 技术场景举例

比如某App的首页推荐列表:

  • 为了减少缓存占用,给首页所有推荐内容的缓存都设置了"24小时过期",并且过期时间都设成了"每天凌晨2点";
  • 凌晨2点一到,所有首页缓存同时失效;
  • 早上8点用户起床刷App,10万用户同时访问首页,缓存里没数据,全去查数据库;
  • 数据库平时QPS(每秒查询量)只有1000,突然承受10万QPS,直接过载宕机,App首页打不开。

4. 怎么解决缓存雪崩?

(1)避免缓存"同时过期"
  • 给缓存过期时间加"随机值":比如原本设24小时过期,改成"24小时±1小时",这样数据会在23-25小时内陆续过期,不会集中失效;
  • 分批次设置过期时间:比如首页推荐分10组,第一组23小时过期,第二组23.5小时,...,第十组25小时,分散过期时间。
(2)避免缓存"突然宕机"
  • 缓存集群化:用Redis集群(主从+哨兵/Redis Cluster),即使主节点宕机,从节点能立刻顶上,避免缓存整体不可用;
  • 缓存降级/熔断:如果缓存真的宕机,暂时用"默认数据"(比如首页推荐显示"热门商品TOP10"的静态数据)或者"拒绝部分非核心请求",不让所有流量冲击数据库(比如用Sentinel/Hystrix做熔断)。
(3)数据库兜底
  • 数据库读写分离:主库写,从库读,缓存失效时请求打到从库,分散压力;
  • 数据库限流:用Nginx/网关限制每秒访问数据库的请求数(比如最多1万QPS),超出的请求返回"稍等再试"。

三、缓存击穿:"某个热门货架塌了,所有人都挤去拿这一件货"

1. 通俗理解

缓存里的某一个热点数据过期了 (其他数据都正常),但此时刚好有大量用户同时访问这个热点数据------这些请求会同时绕开缓存,冲击数据库的"某一行数据",导致数据库这一行的查询压力骤增(虽然整体数据库没崩,但这个热点数据的查询会卡死),这就是缓存击穿。

注意:和"雪崩"的区别是------雪崩是"大量数据同时过期",击穿是"单个热点数据过期"。

2. 举个生活例子

你的便利店有个"爆款冰淇淋",每天能卖1000支:

  • 你平时会把冰淇淋放在门口的冷柜(缓存)里,方便顾客拿,冷柜里的冰淇淋过期时间设为"下午5点";
  • 下午5点一到,冷柜里的冰淇淋刚好卖完且过期(热点数据过期);
  • 这时刚好来了50个顾客,都要买这款冰淇淋(大量并发请求);
  • 你只能让所有人等着,自己跑回仓库拿冰淇淋(所有请求查数据库),仓库里找这一款冰淇淋的过程中,其他顾客全在催,场面混乱(数据库热点行查询压力大)。

3. 技术场景举例

比如某明星官宣结婚,#明星结婚# 这个话题的热度飙升:

  • 微博把"#明星结婚# 的话题详情页数据"存到Redis,过期时间设为10分钟;
  • 10分钟后缓存过期,刚好这时有10万用户同时刷新这个话题(大量并发请求);
  • 缓存里没数据,10万请求同时查数据库的"话题详情表"的这一行数据;
  • 数据库的"话题详情表"中,这一行数据的查询QPS瞬间达到10万,远超数据库单表的承受能力(通常单表QPS上限是1万),导致这一行的查询超时,用户刷新不出来。

4. 怎么解决缓存击穿?

核心思路:让"过期的热点数据"在缓存中"续期",或者让请求"排队查数据库",避免并发冲击

(1)互斥锁(锁机制)
  • 当第一个请求发现缓存过期时,先获取一把"互斥锁"(比如Redis的SETNX命令,只有一个请求能拿到锁);
  • 拿到锁的请求去查数据库,查到后更新缓存,然后释放锁;
  • 其他请求没拿到锁,就每隔100ms重试一次,直到缓存更新完成,再读缓存;
  • 这样就保证了"只有一个请求去查数据库",避免并发冲击。
(2)热点数据永不过期
  • 对于绝对的热点数据(比如爆款商品、热门话题),不设置过期时间,让数据永久存在缓存中;
  • 同时后台开一个"定时任务",每隔一段时间(比如5分钟)去数据库更新一次缓存中的数据,保证数据最新;
  • 缺点:如果数据更新不频繁(比如商品价格很少变),适合用;如果数据更新频繁,可能导致缓存和数据库数据不一致。
(3)缓存预热+提前续期
  • 对热点数据做缓存预热时,设置更长的过期时间(比如1小时);
  • 同时后台开一个"监控任务",每隔30分钟检查一次热点数据的剩余过期时间,如果小于10分钟,就主动去数据库更新缓存,延长过期时间;
  • 避免热点数据在高并发时过期。

四、缓存穿透:"用户要的货根本不存在,所有人都白跑一趟仓库"

1. 通俗理解

用户请求的数据"在缓存里没有,在数据库里也没有"(比如用户查"不存在的商品ID=999999"),导致每次请求都会"绕开缓存,直接查数据库"------如果有大量这样的请求(比如恶意攻击),数据库会被这些"无效请求"打崩,这就是缓存穿透。

注意:和"击穿"的区别是------击穿是"数据在数据库里存在,但缓存过期了",穿透是"数据在数据库里也不存在"。

2. 举个生活例子

有人故意捣乱,每天派100个人来你的便利店,问"有没有外星人饮料"(不存在的商品):

  • 你先看门口的货架(缓存),没有"外星人饮料"(缓存没数据);
  • 你只能跑回仓库找,翻遍所有货架都没有(数据库没数据),然后告诉顾客"没有";
  • 100个人每天都来问,你每天要跑100次仓库,每次都白跑(无效数据库查询),最后累得没力气服务正常顾客(数据库被无效请求打崩)。

3. 技术场景举例

比如某电商平台被恶意攻击:

  • 攻击者写脚本,每秒发送1万次请求,查询"商品ID=1000000000""商品ID=1000000001"...(这些ID在数据库里根本不存在);
  • 每次请求都先查Redis,Redis里没有这些ID的数据(缓存没数据),然后查数据库,数据库里也没有,返回"不存在";
  • 数据库每秒要处理1万次"无效查询",虽然每次查询很快,但累积起来QPS过高,导致数据库CPU占用100%,正常用户查"存在的商品"也查不了。

4. 怎么解决缓存穿透?

核心思路:让"不存在的数据"也在缓存中留个"标记",避免每次都查数据库

(1)缓存空值(Null值缓存)
  • 当请求查"不存在的数据"时(比如商品ID=999999,数据库返回空),也把这个"空结果"写入缓存,设置一个较短的过期时间(比如5分钟);
  • 下次再有人查"商品ID=999999",直接读缓存的空值,不用查数据库;
  • 注意:过期时间不能太长,避免"后续数据库真的新增了这个数据,缓存里还是空值"(比如后来真的有了商品ID=999999,缓存的空值会导致用户看不到新数据)。
(2)布隆过滤器(Bloom Filter)
  • 提前把数据库里"所有存在的key"(比如所有商品ID、所有用户ID)存入布隆过滤器(一种高效的"存在性判断"数据结构);
  • 当用户请求过来时,先通过布隆过滤器判断"这个key是否存在于数据库":
    • 如果布隆过滤器说"不存在",直接返回"没有",不用查缓存和数据库;
    • 如果布隆过滤器说"可能存在"(布隆过滤器有极小的误判率),再查缓存和数据库;
  • 优点:布隆过滤器占用内存极小(比如1亿个商品ID,只需要100MB左右内存),判断速度极快;
  • 缺点:有极小的误判率(比如0.1%),可能把"不存在的key"判断为"可能存在",但不会把"存在的key"判断为"不存在"。
(3)接口限流+恶意请求拦截
  • 对接口做限流(比如每个IP每秒最多请求10次),避免恶意脚本的高频请求;
  • 拦截明显的恶意请求(比如请求的商品ID是负数、或者远超正常范围的ID,直接返回错误)。

总结:4个概念的核心区别

|------|---------------------|--------------|-----------------|
| 概念 | 核心原因 | 影响范围 | 通俗比喻 |
| 缓存预热 | 缓存冷启动,无热点数据 | 首次访问慢 | 货架没备货,需要去仓库拿 |
| 缓存雪崩 | 大量数据同时过期/缓存宕机 | 所有请求冲击数据库 | 所有货架塌了,全去仓库 |
| 缓存击穿 | 单个热点数据过期 | 单个热点的请求冲击数据库 | 单个爆款货架塌了,全去拿这一款 |
| 缓存穿透 | 请求不存在的数据(缓存+数据库都没有) | 无效请求冲击数据库 | 要的货根本没有,全白跑仓库 |

记住:缓存的核心是"减少数据库压力",这4个问题的解决方案,本质都是"尽可能让请求停在缓存层,或者减少无效请求到数据库"。

相关推荐
血手人屠喵帕斯4 小时前
Redis核心原理与Java应用实践
java·数据库·redis
设计师小聂!4 小时前
redis详解 (最开始写博客是写redis 纪念日在写一篇redis)
java·数据库·redis·缓存·bootstrap
她说..4 小时前
Redis的Java客户端
java·数据库·redis·nosql数据库·nosql
Seven974 小时前
Redis有哪些部署方案?了解哨兵机制吗?
redis
麦兜*4 小时前
大模型时代:用Redis构建百亿级向量数据库方
数据库·spring boot·redis·spring·spring cloud·缓存
爱吃烤鸡翅的酸菜鱼19 小时前
Redis六大常见命令详解:从set/get到过期策略的全方位解析
redis
代码的余温1 天前
Redis vs Elasticsearch:核心区别深度解析
大数据·数据库·redis·elasticsearch
贾修行1 天前
Redis 缓存热身(Cache Warm-up):原理、方案与实践
redis·缓存·oracle
凯子坚持 c1 天前
Redis数据类型概览:除了五大基础类型还有哪些?
数据库·redis·缓存