🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
- [1. 为何要使用Redis作为缓存](#1. 为何要使用Redis作为缓存)
- [2. 缓存的更新策略](#2. 缓存的更新策略)
- [3. 缓存预热,缓存穿透,缓存雪崩,缓存击穿(常考面试题)](#3. 缓存预热,缓存穿透,缓存雪崩,缓存击穿(常考面试题))
-
- [3.1 缓存预热](#3.1 缓存预热)
- [3.2 缓存穿透](#3.2 缓存穿透)
- [3.3 缓存雪崩](#3.3 缓存雪崩)
- [3.4 缓存击穿](#3.4 缓存击穿)
1. 为何要使用Redis作为缓存
我们知道,Redis最主要的用途主要有三个方面,首先就是存储数据,也就是把它当做一种内存数据库,其次是缓存,这是Redis最常用的一种场景,然后就是消息队列,不过我们一般不使用Redis作为消息队列,市面上有很多现成的消息队列中间件,比Redis好用的多.
我们为什么要使用Redis作为缓存呢?
首先我们需要知道在计算机中,访问数据的速度CPU寄存器>内存>硬盘>网络,速度快的设备可以作为速度慢的设备的缓存.我们最常见的就是内存作为硬盘的缓存(这也是Redis的定位).当然我们前面也曾经谈到过,硬盘也可以作为网络的缓存.当访问到某个网站上的数据的时候,浏览器就会从服务器上获取数据并进行展示,如果某些数据的体积较大,变化频率又不是很高的时候,就可以通过网络直接存储数据到本地硬盘,当下一次打开页面的时候,就不必从通过网络从服务器上再次获取数据了 .
缓存中存储的数据,一般都是我们经常需要访问的数据 ,这些数据虽然不多,但是却可以满足绝大部分的查询情况,满足的是二八定律,20%的数据可以应对80%的请求.
我们通常使用Redis作为数据库的缓存(MySQL),但是我们知道,数据库的性能并不是很高:
-
首先数据库是把数据存储在硬盘上的,硬盘的读写速度并不快,尤其是随机访问.
-
其次如果查询不能命中索引的话,就需要对表进行遍历,这就会大大增加硬盘读写的次数.
-
如果是一些复杂的查询,比如联合查询,就需要对表进行笛卡尔积的操作,效率更低.
就是因为MySQL这种关系型数据库效率比较低,所以可以承担的并发量比较有限,一旦请求多了,数据库的压力就会很大,甚至发生宕机 .如果想要解决MySQL并发量的问题,我们有两种解决的策略,一种是开源,就是引入更多的机器,构成数据库集群,另一种就是节流,即引入缓存,这就是典型的方案,把一些频繁读取的热点数据,保存到缓存上,后续在查询数据的时候,如果缓存中已经存在了,就可以不再访问MySQL了.Redis也就相当于一个护盾一样,把MySQL给罩住了.
2. 缓存的更新策略
如何知道Redis中存储的是哪些数据呢,换句话说,如何知道哪些数据是热点数据呢?这就涉及到了缓存的更新策略.缓存的更新策略有如下两种:
- 定期生成
顾名思义,就是每隔一定的周期就会挑选出热点数据(比如一天/一周/一个月),Redis会把访问的数据,使用日志的形式来记录下来.此处的热点数据,就可以根据当前这里的统计维度(日志),来定期进行更新.
比如搜索引擎: 在搜索引擎中,所搜索的查询词就是要访问的数据,这时候,系统就会统计搜索的查询词,把这些查询词统计到日志之中.之后就可以对这些日志进行统计了.统计这一天/一周/一个月每个词出现的频率,在根据频率降序排列,取出比如前20%的词作为热点数据,接下来就可以把这些热点词涉及到的搜索结果,提前拎出来,就可以放到类似于Redis这样的缓存中了.
缓存数据总体的流程大体如下:
- 完成统计热词的过程
- 根据热词,找到搜索结果的数据
- 把得到的数据同步到部署缓存中间件的服务器上
- 控制这些缓存服务器自动重启
以上定期缓存策略的优点就是实现起来比较简单,过程更加可控,方便排查问题.
缺点也很明显,就是实时性不够,如果出现一些突发性事件,有一些本来不是热词的内容就会突然成为热词,这些数据就会都打到数据库上,新的热词可能会给数据库带来较大的压力.
- 实时生成
用户在每次查询的时候,系统集群就会经历以下的过程:
- 首先在Redis中查询要查询的数据,如果数据在Redis中存在的话,会直接返回结果.
- 如果Redis中不存在的话,就会从数据库中查询,之后把查询到的结果同时也写入Redis中.
在这种模式之下,经过一段时间的动态平衡,Redis中的key就会逐渐都成为了热点数据.但是如果这样一直不停地写Redis的话,就会达到内存上限(这里的上限不一定是服务器的内存上限,也可能是Redis中的配置项参数,最多可以使用多少内存空间 ,我们可以在conf文件中通过maxmemory
配置项来配置). - 为了解决上述的问题,Redis就引入了=="内存淘汰策略[常见面试题]"==.Redis中的内存淘汰策略主要有以下的几种:
- FIFO(first in first out) 先进先出
把缓存中存在时间最久的(先进来的数据)数据淘汰掉. - LRU(least recently used) 淘汰最近未使用的
记录每个key的最近访问时间,把最近访问的时间最早的key淘汰掉. - LFU(least frequently used) 淘汰访问次数最少的
记录每个key最近一段时间的访问次数,把访问次数最少的淘汰掉. - Random 随机淘汰
随机抽取一名幸运儿淘汰掉
- FIFO(first in first out) 先进先出
举例说明:甄嬛传
我们需要把Redis想想成一个皇帝.把数据想象成后宫佳丽.
FIFO:皇后肯定是最先受宠的,但是现在已经年老色衰了,皇后失宠
LRU:统计最近宠幸的时间,宠幸时间最早的佳丽失宠
LFU:统计最近宠幸的次数,宠幸次数最少的佳丽失宠
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)
• noeviction 默认策略,当内存不足以容纳新写入数据时,新写入操作会报错.
3. 缓存预热,缓存穿透,缓存雪崩,缓存击穿(常考面试题)
3.1 缓存预热
我们上面提到,缓存中更新数据有两种策略,一种是定期生成,一种是实时生成.定期生成不涉及"预热",但是实时生成会涉及到.
Redis服务器在首次接入之后,服务器里是没有任何数据的,此时,所有的请求就都会答到数据库上,如果请求以下子太多,很容易导致数据库服务器宕机.缓存预热就是用来解决上述的问题的,首先通过离线的方式,通过一些统计途径,先把热点数据找到一批,导入到Redis中,此时导入的这批热点数据,就可以帮mysql承担很大的压力了.随着时间的推移,逐渐就会使用新的热点数据淘汰掉旧的热点数据.
3.2 缓存穿透
我们在Redis中查询某个key,在Redis中没有,在mysql中也没有,这个key肯定也不会被更新到Redis中.这次查询没有,下次查还没有,如果像这样的数据存在很多,并且还反复查询,一样会给mysql带来很大的压力 .
为什么会有上面的这种情况呢?有以下的几种情况:
- 业务涉及不合理,比如缺少相关参数的校验,导致非法的key也被查询了.
- 开发/运维人员误删数据库上的数据.
- 黑客恶意攻击
那么如何解决上述内存穿透的问题呢?
- 首先针对第一种场景,我们需要改进业务逻辑,加强监控报警机制,但是这也是一种亡羊补牢的一种方式.更加靠谱的方案,如果发现这个key在Redis中和mysql中都不存在,我们仍然把这个值写入到Redis中,value设置为一个非法的值(比如""),后续在访问非法数据的时候,就会返回设置好的非法的数据.当然我们还可以进入布隆过滤器.每次查询Redis/mysql之前,都判定一下key是否在布隆过滤器上存在.
- 之后就是第二种场景,我们就需要开发/运维人员介入,尽快回复数据.
- 第三种场景,需要安全团队的介入.
3.3 缓存雪崩
由于在短时间内,Redis上大规模的key失效,导致了缓存命中率陡然下降,让数据库的压力陡然上升,甚至发生宕机.
造成上述这种问题的一般有两种情况:
- Redis服务器宕机,或者是Redis的集群大量服务器宕机
- 短时间之内,设置了很多过期时间相同的key.过期的时候也正好是同一时间过期.
解决上述问题的主要方式是:
- 加强监控报警,保证Redis集群的可用性.
- 我们可以选择不给Redis的数据设置过期时间或者是设置过期时间的时候,加入一些随机的因子(避免同一时间过期).
3.4 缓存击穿
缓存击穿相当于缓存雪崩的一种特殊情况,雪崩是大量的key发生了过期,但是穿透与雪崩的区别就是==只是热点数据突然发生了过期,==这就会使得大量的请求打在了数据库上,导致数据库发生宕机.相比雪崩,击穿过期的是热点数据,访问频率更高,影响更大.
解决方案有以下几种:
- 基于统计的方式发现热点key,并设置为永不过期.但是这种往往需要服务器的结构做出较大的调整
- 进行必要的服务降级,例如使用分布式锁,限制同时请求数据库的并发数.我们接下来介绍