Redis及缓存雪崩、缓存穿透等详解
版权声明 :
本文为博主原创文章,如需转载,请注明出处(https://blog.****.net/F1004145107/article/details/88118813)
本文大纲
一、简介
二、Redis持久化方案
三、Redis的缓存雪崩,缓存穿透,缓存预热,缓存更新,缓存降级等问题及解决方案
一 、简介
1.Redis的特点
-
Redis是一种非关系数据库(不会像Mys ql一样多个表直接存在直接关联关系),也是一种内存型数据库
-
Redis是单线程的,并且是直接基于内存的,所以执行效率会非常高
为什呢Redis是单线程的呢?(经典面试题)
官方回答 : 因为Redis是基于内存的,所以CPU不会是Redis的瓶颈,但是内存会是,而且多线程的实现比较麻烦,直接使用单线程更省事一点(PS:这真的是官方说的,虽然说em....有一点...你懂得,但真实原因就是这么简单)
有图为证 :
如果有兴趣的话可以去看一下传送门
因为Redis是直接操作内存的,而且是单线程的,避免了频繁的切换上下文,所以速度非常快
2.Redis的数据结构
-
String : 最普通和常用的一种结构,key/value
-
Hash : 类似于
HashMap
,底层也是哈希表 -
List : 类似于LinkedList,底层是双向链表
-
Set : 类似于HashSet,于HashSet的特性一样,不允许重复数据,不记录元素添加顺序
-
SortSet : 会自动的进行排序的set
-
Streams(流) : 18年刚刚发布的一个新的数据结构,不是太了解,相关资料比较少
二 、Redis持久化方案
1.RDB(默认)
-
RDB是redis默认的持久化方式
-
RDB是采用快照的方式来进行数据持久化的,当符合快照的条件时Redis会自动对内存中的数据进行快照,然后持久化到硬盘中
-
触发条件
-
符合自定义配置的快照规则
-
执行save或者bgsave命令
-
eg : save 60 10000 :表示1分钟内至少100个键被更改则进行快照。
-
-
执行flushall命令
-
执行主从复制操作
-
在redis.conf中设置快照规则
-
打开redis.config文件,202行,这是RDB的默认配置,满足下面三个之一就会被触发
-
在247行,配置快照生成地址
-
在237行可以配置生成快照的名称,默认是dump.rdb
-
-
-
每次redis启动时都会去读取dump.rdb快照文件,将数据加载到内存中
-
原理 : Redis使用fork函数复制一份当前进程的副本(创建一个子进程)
父进程继续接收处理客户端的请求,子进程负责将内存中的数据存储到硬盘中,当子进程写入完所有数据
之后会用新的rdb文件替换旧的rdb文件
-
细节 : 快照的时候并不会修改原有的rdb文件,而是用新生成的替换旧的,所以rdb文件是一定存在的
rdb文件是经过压缩的二进制文件,所以占用内存会很少,并且方便读取
-
优点 : 因为是复制除了一个子进程来实现数据存储,所以父进程还是可以继续响应客户端的请求,所以客户端基
基本不会受到影响,而且rdb是压缩后的二进制文件,进行数据恢复的速度会比较快,所以rbd非常适合用来
做数据备份
-
缺点 : 必须要满足rdb的条件才会执行数据备份,所以有可能因为Redis的突然宕机导致部分数据丢失,所以设置
快照条件时必须足够严谨
2.AOF
-
默认情况下,AOF是处于关闭状态的
-
只要当前指令会修改Redis中的数据,那么Redis就会将这条命令存储到硬盘中,比较消耗资源,当然我们也可以通过固态硬盘等硬件来提升性能
-
操作 :
-
可以通过修改 redis.conf 配置文件593行中 appendonly 参数开启 AOF 方式
-
默认的文件名是 appendonly.aof,可以通过597行的 appendfilename 参数修改
-
可以通过623行的appendfsync来修改写入策略
-
everysec(默认) : 每秒写一次
-
always : 每次有redis命令执行时就写一次,比较安全,但是效率较低
-
no : 由系统决定什么是后写入,有可能导致数据丢失
-
-
是否在重写文件时进行写入操作,645行
-
no(默认) : 会阻塞正在重写的进程,执行写入操作
-
yes : 不会阻塞正在重写的进程,等到重写完成后再进行写入操作,性能较高
-
-
设置重写的文件增长比例以及最小内存,664行
-
percentage : 表示当前 aof 文件大小超过上一次 aof 文件大小的百分之多少的时候会进行重写。
-
min-size : 重写时最小的aof内存大小
-
-
异常处理机制,689
-
yes(默认) : 如遇到停电等异常,在恢复之后会继续重写
-
no : 恢复之后直接失败
-
-
-
原理 :
-
Redis可以在AOF文件太大时都AOF中的命令进行重写,重写后的AOF文件中是恢复全部数据的最小命令
合集(比如有一个key修改俩次的命令,那么只会保留最后一次的修改命令)
-
重写操作是安全的,在重写过程中并不会删除掉旧的AOF文件,并且如果有新的命令还是会继续加入到旧
的AOF文件中,当新的AOF文件完成后会对旧的AOF文件进行替换,并且将重写过程中新加入到旧的AOF中
的命令追加到新的AOF文件中
-
AOF文件中保存的命令都是有序的,而且都是以Redis协议的格式进行保存的,所以容易读懂
-
-
AOF的修复
-
如果Redis此时正在对AOF文件进行写入操作,此时如果Redis突然宕机的话,AOF就会受损,Redis不会加载破损的AOF文件
-
修复步骤 :
-
将当前的AOF进行备份
-
使用Redis自带的redis-check-aof 程序,对原来的 AOF 文件进行修复.redis-check-aof --fix
-
重启Redis服务器,让其重新加载AOF文件
-
-
3.RDB及AOF的选择
-
如果对数据的安全性要求非常高的话,那么最好俩个一起开启,如果能允许分钟内的数据丢失的话,就选择RDB
-
如果数据需要时常备份的话,最好开启RDB
-
俩中可以同时开启,也可以只使用一个,但是如果都开启的话,Redis重启时只会加载AOF文件来恢复数据
三 、Redis会遇到的问题以及解决方案
1.缓存雪崩
-
发生场景 : 当Redis服务器重启或者大量缓存在同一时期失效时,此时大量的流量会全部冲击到数据库上面,数据库有可能会因为承受不住而宕机
-
解决方案 :
-
均匀分布 : 我们应该在设置失效时间时应该尽量均匀的分布,比如失效时间是当前时间加上一个时间段的随机值
-
熔断机制 : 类似于SpringCloud的熔断器,我们可以设定阈值或监控服务,如果达到熔断阈值(QPS,服务无法响应,服务超时)时,则直接返回,不再调用目标服务,并且还需要一个检测机制,如果目标服务已经可以正常使用,则重置阈值,恢复使用
-
隔离机制 : 类似于Docker一样,当一个服务器上某一个tomcat出了问题后不会影响到其它的tomcat,这里我们可以使用线程池来达到隔离的目的,当线程池执行拒绝策略后则直接返回,不再向线程池中增加任务
-
限流机制 : 其实限流就是熔断机制的一个版本,设置阈值(QPS),达到阈值之后直接返回
-
双缓存机制 : 将数据存储到缓存中时存储俩份,一份的有效期是正常的,一份的有效期长一点.不建议用这个方案,因为比较消耗内存资源,毕竟Redis是直接存储到内存中的
-
2.缓冲穿透
-
发生场景 : 此时要查询的数据不存在,缓存无法命中所以需要查询完数据库,但是数据是不存在的,此时数据库肯定会返回空,也就无法将该数据写入到缓存中,那么每次对该数据的查询都会去查询一次数据库
-
解决方案 :
-
布隆过滤 : 我们可以预先将数据库里面所有的key全部存到一个大的
map
里面,然后在过滤器中过滤掉那些不存在的key.但是需要考虑数据库的key是会更新的,此时需要考虑数据库 -->map
的更新频率问题 -
缓存空值 : 哪怕这条数据不存在但是我们任然将其存储到缓存中去,设置一个较短的过期时间即可,并且可以做日志记录,寻找问题原因
-
3.缓存预热
-
其实这个不是一个问题,是一种机制,在上线前先将需要缓存的数据放到缓存中去,这个的实现很简单,可以在启动的时候放(数据比较小),做一个开关(一个隐秘的接口),定时刷新缓存
4.缓存更新
-
这也不是一个问题,是一种机制,怎么样保证缓存中的key是实时有效的,以及及时的更新数据资源
-
监测机制 : 定时去监测Redis,查看过期的缓存,
问题 :
在看到这里的时候我有一个问题,如果key过期了那么我要不要再将key重新放入缓存呢,如果放入的话我设置这个有效期就完全没有必要了,完全可以设置为永久有效
我想了一个解决方案,我们可以对命中率做一个记录,如果这个key在最近一段时间内被频繁命中的话,我们就在失效时进行更新,否则就直接清除掉
-
被动更新 : 每次请求过来时我们判断一下当前key是否失效,失效就重新查询存放到缓存中,这个问题不会涉及到监测机制那个问题
-
5.服务降级
-
服务降级是不得已而为之的,在关键的时候丢卒保帅,保证核心功能正常运行
-
服务拒绝 : 直接拒绝掉非核心功能的所有请求,其实基本就是直接废弃掉某些模块
-
服务延迟 : 将请求加入到线程池中或队列中,延迟执行这些请求
-
-
注意 : 服务降级一定要有对应的恢复策略,不能降下去就不回来了,我们可以监测服务的状态,当状态适当时恢复服务的正常使用
参考资料 :