


专栏:Redis 修行录
个人主页:手握风云
目录
一、什么是缓存
缓存是计算机领域中一个非常经典的概念,在各类系统中都有广泛使用。缓存的核心思路,就是把经常需要使用的数据,存放到访问速度更快、读取更便捷的存储位置,以此实现快速读取。比如乘坐高铁时需要反复刷身份证,如果身份证放在大容量的皮箱里,每次使用都要开箱寻找,十分麻烦;如果提前放到空间更小、拿取更方便的衣服口袋中,每次核验直接掏出即可。这里的口袋就相当于缓存,皮箱对应原始存储介质,借助缓存能够显著提升数据获取的效率。
硬件设备之间的访问速度存在明确差异,一般排序为:CPU 寄存器 > 内存 > 硬盘 > 网络。基于这个速度差异,就可以形成多级缓存:硬盘可以作为网络的缓存,内存可以作为硬盘的缓存,CPU 寄存器又可以作为内存的缓存。硬件设备普遍存在这样的规律:访问速度越快,硬件的采购成本就越高,但可提供的存储空间反而越小。
受限于存储空间,缓存不适合存放全部数据,一般只会保存热点数据,也就是被频繁访问的数据。结合二八定律理解,系统中 20% 的热点数据,就能够覆盖 80% 的访问请求。所以只需要把这一小部分高频访问的数据放到缓存当中,就可以应对绝大多数业务访问场景,让系统整体性能得到明显提升。
二、Redis 作为缓存
在网站开发当中,我们通常会选用MySQL这类关系型数据库完成数据持久化存储。关系型数据库功能完备,但也存在明显短板,查询操作会消耗较多系统资源,整体性能有限。
造成关系型数据库性能不高的原因有很多。首先,数据库的数据存放在硬盘,硬盘I/O速度本身比较慢,随机访问的效率更低。其次,如果查询没有命中索引,就会触发全表遍历,进一步增加硬盘I/O次数。除此之外,数据库执行SQL语句,还要完成解析、校验、优化等一系列工作;遇到多表联合查询这类复杂操作,需要做笛卡尔积运算,查询效率还会进一步下降。
当系统并发访问量很高时,数据库就会承受巨大压力。服务器处理每一条请求,都要消耗CPU、内存、硬盘、带宽等硬件资源,而服务器硬件资源总量是有限的。大量请求同时到来,资源会被快速耗尽,后续请求得不到处理,严重情况下还会造成数据库服务崩溃宕机。
想要提升数据库的并发承载能力,主要有两种解决思路。第一种是“开源”,通过增加硬件资源,搭建数据库集群,借助主从复制、分库分表等技术扩展数据库能力。第二种是“节流”,引入缓存组件,把高频访问的热点数据保存到缓存中,以此减少直接访问数据库的请求数量。实际项目开发里,一般会把这两种方案搭配在一起使用。
Redis就是非常主流的数据库缓存解决方案。相比于MySQL,Redis处理请求消耗的系统资源更少,能够支撑更大的并发量。一方面Redis的数据全部存放在内存当中,内存访问速度远远高于硬盘;另一方面Redis只做简单的键值对存储,不需要处理复杂SQL相关的各类逻辑,就像一层护盾,保护后端的MySQL数据库。
完整的缓存查询流程是这样的:客户端向业务服务器发起查询请求,业务服务器会优先查询Redis。如果Redis中已经存在目标数据,就直接把数据返回给客户端,不需要访问MySQL;如果Redis中没有对应数据,再去查询MySQL数据库。

依据二八定律,只要把20%的热点数据存入Redis,就可以让80%的请求不用访问数据库。当然不同业务场景比例会有变化,可能是一九或者三七,但绝大多数场景下,缓存都可以显著提升系统访问效率,降低数据库压力。这里需要特别注意,缓存只能够加快读操作的速度。对于写操作,依旧需要操作数据库,缓存无法提升写请求的性能。
三、缓存更新&淘汰策略
3.1. 热点数据生成方式
- 定期生成
缓存的核心价值在于存放热点数据,但如何筛选出真正的高频访问数据,是缓存机制需要解决的核心问题,对应的热点数据生成方式主要分为定期生成和实时生成两类。
定期生成的思路是按照固定周期统计数据的访问频次,筛选出访问量排名靠前的数据作为热点。以搜索引擎场景为例,服务器会通过日志完整记录每个用户的搜索词和访问时间,每隔一段时间就会对周期内的海量日志做统计分析,这个过程通常需要借助 Hadoop、Spark 等大数据处理工具完成,最终生成高频词表。但这种方式存在明显短板,就是实时性较差,很难应对突发的流量热点,比如春节期间 “春晚” 相关搜索量会瞬间暴涨,定期统计就无法及时把这类突发热词纳入缓存。
- 实时生成
实时生成则不需要提前做统计分析,而是通过设定缓存容量上限来动态筛选热点数据。可以通过 Redis 配置文件中的 maxmemory 参数设定内存上限,用户每次发起查询时,如果目标数据在 Redis 中存在就直接返回结果,如果不存在就从数据库读取数据,同时把查询结果写入 Redis。当缓存占用空间达到设定的容量上限时,就会触发缓存淘汰策略,移除一部分相对冷门的数据。按照这个机制持续运行一段时间后,Redis 中留存下来的自然就是访问频率更高的热点数据。
3.2. 通用缓存淘汰算法
通用的缓存淘汰策略主要有四种,这些思路并不局限于 Redis,各类缓存系统都可以参考使用。第一种是 FIFO 先进先出策略,按照数据存入缓存的时间排序,优先淘汰最早存入的内容。第二种是 LRU 最近最少使用策略,会记录每个键的最近访问时间,优先淘汰距离上次访问时间最久的数据。第三种是 LFU 访问频率最少策略,统计每个键在一段时间内的访问次数,淘汰访问次数最少的数据。第四种是 Random 随机淘汰策略,不做访问特征统计,直接从所有键中随机选择数据移除。
3.3. Redis 内置淘汰策略
分为两类:volatile‑xxx 只针对设置过期时间的 key;allkeys‑xxx 针对全部 key。
- volatile‑lru:过期 key 中执行 LRU 淘汰
- allkeys‑lru:全部 key 中执行 LRU 淘汰
- volatile‑lfu(Redis4.0+):过期 key 中执行 LFU 淘汰
- allkeys‑lfu(Redis4.0+):全部 key 中执行 LFU 淘汰
- volatile‑random:过期 key 随机删除
- allkeys‑random:全部 key 随机删除
- volatile‑ttl:过期 key 里优先删除快要到期的 key
- noeviction【默认】:内存满,拒绝写入,直接报错。
四、缓存四大问题
缓存机制在实际业务运行中,会面临四类典型的问题,分别是缓存预热、缓存穿透、缓存雪崩与缓存击穿,每一类问题都有对应的产生场景和成熟的解决方案。
4.1. 缓存预热
缓存预热是缓存上线或重启阶段的典型问题。当 Redis 服务刚启动,或是缓存中大批 key 集中失效时,Redis 内部几乎没有可用的缓存数据,此时绝大多数访问请求会直接穿透到后端的 MySQL 数据库,很容易给数据库造成过大压力。因此需要在流量到来前,提前将统计得到的热点数据批量写入 Redis 中,让缓存尽快为数据库撑起流量护盾。预热用的热点数据不需要做到绝对精准,只要能抵挡大部分常规请求即可,随着系统持续运行,缓存内的数据会自动动态调整,逐步适配当前的访问特征。
缓存穿透指的是请求访问的 key,在 Redis 缓存和后端数据库中都不存在的场景。由于对应的数据不存在,这类请求的结果不会被写入缓存,后续重复发起相同请求时,依然会每次都访问到数据库,持续给数据库带来不必要的压力。造成缓存穿透的原因有多种,可能是业务设计存在缺陷,缺少必要的参数校验环节,导致非法的 key 也能进入查询流程;也可能是开发或运维误操作,意外删除了数据库中的部分数据;还有可能是黑客发起恶意攻击,刻意构造大量不存在的 key 发起请求。
4.2. 缓存击透
针对缓存穿透问题,行业内有三种常见的解决思路。第一种是在入口层做严格的参数合法性校验,比如查询的 key 是手机号时,先校验格式是否合法,直接拦截明显非法的请求;第二种是缓存空值,即使数据库中查询不到对应数据,也往 Redis 中写入一个空值并设置较短的过期时间,避免后续重复请求打到数据库;第三种是引入布隆过滤器,它结合了哈希与位图的设计思想,能够用极少的空间成本提前判定 key 是否存在,不存在则直接拦截,不需要再查询缓存和数据库。
4.3. 缓存雪崩
缓存雪崩是影响范围更大的一类缓存故障,指的是短时间内有大量缓存 key 集中失效,或是 Redis 服务整体宕机,导致数据库承受的访问压力骤增,严重时会直接引发数据库宕机。原本 Redis 就像一层护盾,替数据库抵挡了绝大多数读请求,一旦这层护盾突然失效,全量流量瞬间冲击后端数据库,很容易超出数据库的承载上限。
造成缓存雪崩的原因主要分为两类,一类是 Redis 服务本身出现故障停止服务,另一类是缓存中大量 key 设置了完全相同的过期时间,在同一时刻集中到期失效。对应的解决方案也从两方面入手:一方面部署高可用的 Redis 集群,搭配完善的监控报警体系,避免缓存服务出现单点故障;另一方面在设置 key 过期时间时,增加随机时间因子,让各个 key 的过期时间分散开,避免大批量 key 同时失效,也可以根据业务场景给部分核心数据不设置过期时间。
4.4. 缓存击穿
缓存击穿可以看作是缓存雪崩的一种特殊情况。需要说明的是,缓存击穿的英文为 Cache breakdown,直译容易和缓存穿透混淆,它描述的不是数据不存在的场景,而是某一个访问量极高的热点 key 突然过期,导致瞬间海量的并发请求全部绕过缓存,直接访问后端数据库,同样可能引发数据库宕机。和缓存雪崩相比,击穿的影响范围集中在单个热点 key,但因为对应请求量极高,同样具备很强的破坏力。
针对缓存击穿问题,常用的解决方式有两种。一种是通过访问统计提前识别出热点 key,为这类 key 设置永不过期,从根源上避免过期引发的流量冲击;另一种是做服务降级与流量控制,比如在查询数据库时使用分布式锁,限制同时访问数据库的并发请求数,避免瞬时流量压垮数据库。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2401_85198927/article/details/165121176




