【redis】缓存 更新策略(定期、实时生存),缓存预热、穿透、雪崩、击穿详解

发布于:2025-03-30 ⋅ 阅读:(34) ⋅ 点赞:(0)

什么是缓存

redis 最常用的场景

核心思路就是把一些常用的数据,放到触手可及(访问速度更快)的地方

  • ⽐如我需要去⾼铁站坐⾼铁. 我们知道坐⾼铁是需要反复刷⾝份证的 (进⼊⾼铁站, 检票, 上⻋,乘⻋过程中, 出站…)
  • 正常来说, 我的⾝份证是放在⽪箱⾥的(⽪箱的存储空间⼤, ⾜够能装). 但是每次刷⾝份证都需要开⼀次⽪箱找⾝份证, 就⾮常不⽅便.
  • 因此我就可以把⾝份证先放到⾐服⼝袋⾥. ⼝袋虽然空间⼩, 但是访问速度⽐⽪箱快很多.
  • 这样的话每次刷⾝份证我只需要从⼝袋⾥掏⾝份证就⾏了, 就不必开⽪箱了.
  • 此时 “⼝袋” 就是 “⽪箱” 的缓存. 使⽤缓存能够⼤ 提⾼访问效率

速度快的设备,可以作为速度慢的设备的缓存

  • CPU 寄存器 > 内存 > 硬盘 > 网络

最常见的是,使用内存作为硬盘的缓存(redis 定位)

硬盘也可以作为网络的缓存——浏览器的缓存

  • 浏览器通过 http/https 从服务器上获取到数据(htmlcssjs、图片、视频、音频、字体…)并进行展示
    • 从服务器上获取:网络
    • 图片、音频、视频等等这样体积大,又不太会改变的数据,就可以保存到浏览器本地(浏览器所在主机的硬盘上),后续再打开这个页面,就不必重新从网络获取上述数据了

缓存能够有意义,二八定律

  • 缓存虽然快,但是空间小
  • 20% 的数据,可以应对 80% 的请求

使用 redis 作为缓存

通常是使用 redis 作为数据库的缓存(MySQL

  • 数据库是一个非常重要的组件,绝大部分商业项目中都会涉及到
  • 并且 MySQL 的速度又比较慢

为什么说关系型数据库性能不高?

  1. 数据库把数据存储在硬盘上,硬盘的 IO 速度并不快,尤其是随机访问
  2. 如果查询不能命中索引,就需要进行表的遍历,这就会大大增加硬盘 IO 次数
  3. 关系型数据库对于 SQL 的执行会做一系列的解析,校验,优化工作
  4. 如果是一些复杂查询,比如联合查询,需要进行笛卡尔积操作,效率更是降低很多

因为 MySQL 等数据库,效率比较低,多以承担的并发量就有限,一旦请求数多了,数据库的压力就会很大,甚至很容易就宕机了

  • 服务器每次处理一个请求,一定都要消耗一些硬件资源(CPU、内存、硬盘、网络…)任意一种资源的消耗超出了机器能提供的上限,机器就很容易出现故障了

如何提高 MySQL 能承担的并发量?(客观需求)

  1. 开源:引入更多的机器,构成数据库集群
  2. 节流:引入缓存,就是典型的方案,把一些频繁读取的数据,保存到缓存上。后续在查询数据的时候,如果缓存中已经存在了,就不再访问 MySQL

image.png

  • 虽然 redis 上只能存少数数据,但是大部分请求都是使用的这少数的热点数据
  • 实际业务中,3:71:9 无所谓,基本思想是一致的

缓存更新策略

如何知道 redis 中应该存储哪些数据?
如何知道哪些数据是热点数据呢?

这里就要谈到“缓存的更新策略

1. 定期生成

会把访问的数据,以日志的形式记录下来

比如一个搜索引擎,“查询词”就是要关注的“访问的数据”

  • 我们可以通过日志,把搜索框里面都用到了哪些词,给记录下来,就可以针对这些日志进行统计了
  • 统计这一天,每个词出现的频率,再根据频率降序来排序,再取出前 20% 的词,就可以把这些词认为是“热点词
    • 数据量可能大到说,一台机器存不下,就需要使用分布式的系统来存储这些日志(HDFS)
    • 再使用 Hadoopmap-reduce 来写代码进行统计;也可以基于 HDFSHBASE 这样的数据库来写 SQL 统计
    • 这些就是大数据工程师的日常工作
  • 接下来就可以把这些热点词,涉及到的搜索结果,提前拎出来,就可以放到类似于“redis”这样的缓存中了

此处的数据,就可以根据当前这里统计的未读,来定期更新

  • 按照天级别统计,就每天更新一次
  • 按照月级别统计,就每个月更新一次
    写一套离线的流程(往往使用 shellPython 写脚本代码),可以通过定时任务来触发
  1. 完成统计热词过程
  2. 根据热词,找到搜索结果的数据(广告数据)
  3. 把得到缓存数据同步到缓存服务器上
  4. 控制这些缓存服务器自动启动

优点:上述过程,实际上实现起来比较简单,过程更可控(缓存中有什么是比较固定的),方便排查问题
缺点:实时性不够。如果出现一些突发性时间,有一些本来不是热词的内容,成了热词了,新的热词就可能会给后面的数据库什么的带来比较大的压力

  • 比如,过年的时候,“春节晚会”就是突发性的热词

2. 实时生成

  • 如果在 redis 查到了,就直接返回
  • 如果 redis 中不存在,就从数据库查,把查到的结果同时也写入 redis

这样不停地写 redis,就会使 redis 的内存占用越来越多,就会逐渐达到内存上限

  • 不一定是机器内存上限,redis 中也可以配置,最多使用多少内存(maxmermory 参数)

如果继续往里插入数据,就会触发问题,为了解决上述问题,redsi 就引入了“内存淘汰策略

内存淘汰策略

#高频面试

先关策略

FIFOFirst In First Out)先进先出
把缓存中存在时间最久的(也就是先来的数据)淘汰掉

LRU (Least Recently Used) 淘汰最久未使用的
记录每个 key 的最近访问时间,把最近访问时间最老的 key 都淘汰掉

LFULeast Frequently Used)淘汰访问次数最少的
记录每个 key 最近一段时间的访问次数,把访问次数最少的淘汰掉

Random 随机淘汰
从所有的 key 中抽取幸运儿被随机淘汰

理解上述几种淘汰策略:
想象你是个皇帝,有后宫佳丽三千。虽然你是真龙天子,但是经常宠信的妃子也就那么寥寥数人(后宫佳丽散散,相当于数据库中的全量数据,经常宠信的妃子相当于热点数据,是放在缓存中的)
今年选秀的一批新的小主,其中一个被你看上了,宠信新人,自然就需要有旧人被冷落,到底谁是要被冷落的人呢?

  • FIFO:皇后是最先受宠的,现在已经年老色衰了==>皇后失宠
  • LRU:统计最近宠幸时间,皇后(一周前),熹妃(昨天),安答应(两周前),华妃(一个月前)==>华妃失宠
  • LFU:统计最近一个月的宠幸次数,皇后(3 次),熹妃(15 次),安答应(1 次),华妃(10 次)==>安答应失宠(最合理
  • Random:随机挑选一个妃子失宠(最不合理
    具体采取哪种策略,要结合实际场景来具体问题具体分析
相关配置项

redis 里面,有一个配置项,就可以设置 redis 采取上述哪种策略淘汰内存数据

  • volatile-lru 当内存不⾜以容纳新写⼊数据时,从设置了过期时间key 中使⽤ LRU(最近最少使⽤)算法进⾏淘汰

    • 设置了过期时间的就算,包括过期时间还没到的
  • allkeys-lru 当内存不⾜以容纳新写⼊数据时,从所有 key 中使⽤ LRU(最近最少使⽤)算法进⾏淘汰.

  • volatile-lfu 4.0版本新增,当内存不⾜以容纳新写⼊数据时,在过期key 中,使⽤ LFU 算法进⾏删除 key.

  • allkeys-lfu 4.0版本新增,当内存不⾜以容纳新写⼊数据时,从所有 key 中使⽤ LFU 算法进⾏淘汰.

  • volatile-random 当内存不⾜以容纳新写⼊数据时,从设置了过期时间的 key 中,随机淘汰数据.

  • allkeys-random 当内存不⾜以容纳新写⼊数据时,从所有 key 中随机淘汰数据.

  • volatile-ttl 在设置了过期时间的 key 中,根据过期时间进⾏淘汰,越早过期的优先被淘汰.(相当于 FIFO, 只不过是局限于过期的 key)

    • 对于其他没有设置过期时间的 key,很可能是没有保存设置时间的
  • noeviction 默认策略,当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错.

    • 不适合于实时更新策略

经过一段时间的“动态平衡”,redis 中的 key 就逐渐都成了热点数据了

缓存预热(Cache preheating)

#高频面试
缓存中的数据

  1. 定期生成(这种情况,不涉及“预热”)
  2. 实时生成

redis 服务器首次接入之后,服务器里面是没有数据

  • 客户端先查询 redis,如果没有查到,就再查一次 MySQL,查到了之后,会把数据也写到 redis
  • 服务器里没有数据,此时所有的请求都会打给 MySQL,随着时间的推移,redis 上的数据越积累越多,MySQL 承担的压力就逐渐减小了

缓存预热,就是用来解决上面问题的

把定期生成和实时生成结合一下。先通过离线的方式,通过一些统计的途径,先把热点数据找到一批,导入到 redis 中。此时导入的这批热点数据,就能帮 MySQL 承担很大的压力了。随着时间推移,逐渐就使用新的热点数据淘汰掉旧的数据

缓存穿透(Cache penetration)

#高频面试
查询的某个 key,在 redis 中没有,MySQL 中也没有,这个 key 肯定也不会被更新到 redis

  • 这次查询:没有;下次查询:仍然没有…
  • 如果像这样的数据,存在很多,并且还反复查询, 一样也会给 MySQL 带来很大的压力

为何产生?

  • 业务涉及不合理。比如缺少必要的参数校验环节,导致非法的 key 也被进行查询了
    • 典型情况
  • 开发/运维误操作。不小心把部分数据从数据库上误删了
    • 没那么典型,表现也是缓存穿透。误删操作,不一定能及时发现
  • 黑客恶意攻击
    • 比较少见。黑客是很多的,一些中大厂,都有专门负责做安全的团队(有人负重前行)

如何解决?

通过改进业务/加强监控报警等方式,都是亡羊补牢

相比之下更靠谱的方案:(降低问题的严重性**)

  1. 如果发现这个 keyredisMySQL 上都不存在,仍然写入 redis 中,value 设成一个非法值(比如""
  2. 还可以引入布隆过滤器。每次查询 redis / MySQL 之前,都先判定一下 key 是否在布隆过滤器上存在(把所有的 key 都插入到布隆过滤器中)
    • 布隆过滤器,本质上是结合了 hash+bitmap,以比较小的空间开销,比较快的时间速度,实现针对 key 是否存在的判定

缓存雪崩(Cache avalanche)

#高频面试
由于在短时间内,redis 上大规模的 key 失效,导致缓存命中率陡然下降,并且 MySQL 的压力迅速上升,甚至直接宕机

  1. redis 直接挂了
    • redis 宕机/ redis 集群模式下,大量节点宕机
  2. redis 好着呢,但是可能之前短时间内设置了很多 keyredis,并且设置的过期时间是相同的
    • redis 里设置 key 作为缓存的时候,有的时候为了考虑缓存的时效性,就会设置过期时间(和 redis 内存淘汰机制,是配合使用的)

如何解决?

  1. 加强监控报警,加强 redis 集群可用性的保证
  2. 不给 key 设置过期时间/过期时间的时候添加随机的因子(避免同一时刻过期)

缓存击穿(Cache breakdown)

#高频面试
相当于缓存雪崩的特殊情况。针对热点 key,突然过期了,导致大量的请求直接访问到数据库上,甚至引起数据库宕机

  • 热点 key 访问频率高,影响更大

如何解决?

  • 基于统计的方式发现热点 key,并设置永不过期
    • 往往需要服务器结构做出较大的调整
  • 进行必要的服务降级。例如访问数据库的时候用分布式锁,限制同时请求数据库的并发数
    • 本身服务器的功能有十个,但是在特定的情况下,适当的关闭一些不重要的功能,只保留核心功能(服务降级)==>手机的省点模式
    • 通过分布式锁,限制数据库的访问频率

总结

  1. 缓存的基本概念
  2. 如何使用 redis 作为缓存
  3. 缓存更新策略==> redis 内存淘汰机制
  4. 缓存使用的注意事项

网站公告

今日签到

点亮在社区的每一天
去签到