(必看)java面试重难点之数据库专题-Redis

未完待续,持续更新,如果有用,记得点赞收藏关注


1、redis为什么这么快
2、什么是上下文?
3、为什么要⽤ redis
4、redis和memcached的区别
5、缓存雪崩
6、缓存击穿
7、缓存穿透
8、布隆过滤器是什么
9、redis的删除方式
10、讲讲Redis内存淘汰机制
11、Redis持久化方式有哪些、
12、AOF和RDB同时开启,redis听谁的?
13、RDB与AOF的对比
14、如何保证缓存与数据库双写时的数据⼀致性?

1、redis为什么这么快

(一)纯内存操作
(二)单线程操作,避免了多线程频繁上下文切换带来的损耗。
(三)采用了非阻塞I/O多路复用机制

2、什么是上下文?

上下文是指某一时间点 CPU 寄存器和程序计数器的内容。
寄存器是 CPU 内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内存)。
寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。 程序计数器是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置,具体依赖于特定的系统。
上下文切换(有时也称做进程切换或任务切换)是指 CPU 从一个进程或线程切换到另一个进程或线程。
上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动:(1)挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处,(2)在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复,(3)跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。
上下文切换有时被描述为内核挂起 CPU 当前执行的进程,然后继续执行之前挂起的众多进程中的某一个。尽管这么说对于澄清概念有所帮助,但是这句话本身可能有一点令人困惑。因为通过定义可以知道,进程是指一个程序运行的实例。所以说成挂起一个进程的运行可能更适合一些。

3、为什么要⽤ redis

1、高性能:读取速度块,第一次读取时,缓存中没有从数据库中读取并将数据存入缓存中,第二次读取从缓存中读取。
2、高并发:可以同时接受很多请求,比数据库能承受的请求量多得多。

4、redis和memcached的区别



5、缓存雪崩

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key

解决方案:

1)缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

2)如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。

3)设置热点数据永远不过期。

6、缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案:

1)设置热点数据不过期。     

2)加互斥锁。(涉及到分布式锁的一个排坑功能)

7、缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:

(1) 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟
(2) 设置可访问的名单(白名单):
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
(3) 采用布隆过滤器:
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
(4) 进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

8、布隆过滤器是什么

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
布隆过滤器添加元素:
将要添加的元素跟k个hash函数计算求得k个hash值,并把每个hash值指向的bit数组位置的值置为1
布隆过滤器查询元素:
将要查询的元素与k个hash函数进行计算求个k个hash值,判断这k个hash值指向的bit数组位置总值是否都为1,如果不全为1,证明该元素一定不存在。如果全为1,证明这个元素可能存在。
因为当多个元素与hash函数进行计算后,指向的bit数组位置很可能重复置为1,进行覆盖,无法判断是哪个元素让其数组值为1的。

布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为m,哈希函数的个数为k
如果我们要映射一个值到布隆过滤器中,我们需要使用多个不同的哈希函数生成多个哈希值,并对每个生成的哈希值指向的 bit 位置 1,例如针对值 “baidu” 和三个不同的哈希函数分别生成了哈希值 1、4、7,则上图转变为:
Ok,我们现在再存一个值 “tencent”,如果哈希函数返回 3、4、8 的话,图继续变为:

值得注意的是,4 这个 bit 位由于两个值的哈希函数都返回了这个 bit 位,因此它被覆盖了。现在我们如果想查询 “dianping” 这个值是否存在,哈希函数返回了 1、5、8三个值,结果我们发现 5 这个 bit 位上的值为 0,说明没有任何一个值映射到这个 bit 位上,因此我们可以很确定地说 “dianping” 这个值不存在。而当我们需要查询 “baidu” 这个值是否存在的话,那么哈希函数必然会返回 1、4、7,然后我们检查发现这三个 bit 位上的值均为 1,那么我们可以说 “baidu” 存在了么?答案是不可以,只能是 “baidu” 这个值可能存在。
这是为什么呢?答案跟简单,因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个值 “taobao” 即使没有被存储过,但是万一哈希函数返回的三个 bit 位都被其他值置位了 1 ,那么程序还是会判断 “taobao” 这个值存在。

9、redis的删除方式

定期删除:redis每隔100ms随机抽取一些设置了过期时间的key,检查是否过期,过期了则删除。这种方式是随机的,因此会有过期的key遗留下来,剩下的进行惰性删除。

惰性删除:对于遗留下来的过期key,只有系统进行查询才会被删除,不查询则不删除。

10、Redis内存淘汰机制

第一类:从已经设置过期时间的数据集中筛选(最近最少用,最不常用,任意数据,将要过期)

第二种:在所有数据集中筛选(最近最少用,最不常用,任意数据)


第三种:
volatile为前缀的策略都是从已过期的数据集中进行淘汰。 allkeys为前缀的策略都是面向所有key进行淘汰。 LRU(least recently used)最近最少用到的。 LFU(Least Frequently Used)最不常用的。 它们的触发条件都是Redis使用的内存达到阈值时。

11、Redis持久化方式有哪些

RDB(快照):将当前的数据以快照的形式存入磁盘中。实际就是fork一个子进程,先将数据集体写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
详解:Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失
AOF(append-only file):以日志的形式进行记录,AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
详解:AOF日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

13、RDB与AOF的对比

13、AOF和RDB同时开启,redis听谁的?

AOF和RDB同时开启,系统默认取AOF的数据(因为AOF数据不会存在丢失

14、如何保证缓存与数据库双写时的数据⼀致性?

1、延时双删

为什么要用延时双删?

1、A线程先删除缓存再删除数据库,是为了让其他线程不是从缓存中拿到的是旧数据,但这种也会导致其他线程在删除数据前,从数据中读取到了数据又更新到缓存中

2、A线程删除缓存后再删除数据库再删除缓存,有可能导致B线程在更新数据库读取到了数据库的数据更新到缓存,给C线程读取了

3、所以采用延时双删,在B线程读取数据库更新缓存后再删除缓存,此时C线程读取到的缓存为空,就会从数据库中获取

分以下几种情况考虑
一、只先删缓存
问题:先删缓存,在改库前,其他事务又把旧数据放到缓存里去了。

二、只后删缓存
问题:改了库,清理缓存前,有部分事务还是会拿到旧缓存
三、普通双删
问题:第一次清空缓存后、更新数据库前:其他事务查询了数据库hang住 第二次清空缓存后:其他事务更新缓存,此时又会把旧数据更新到缓存


四、为什么需要延时双删?

在三中,第二次清空缓存之前,多延时一会儿,等B更新缓存结束了,再删除缓存,这样就缓存就不存在了,其他事务查询到的为新缓存。

延时是确保 修改数据库 -> 清空缓存前,其他事务的更改缓存操作已经执行完。

五、以上策略还能不能完善
四中说到,采用延时删最后一次缓存,但这其中难免还是会大量的查询到旧缓存数据的。
这时候可以通过加锁来解决,一次性不让太多的线程都来请求,另外从图上看,我们可以尽量缩短第一次删除缓存更新数据库的时间差,这样可以使得其他事务第一时间获取到更新数据库后的数据。另外,该方式(第5种,相对第2种,只后删缓存的,可以大大的减少获取到旧缓存的数量)

2、使用队列

https://juejin.cn/post/6844903849946267661
#面经##java工程师##春招##秋招##实习#
全部评论
楼主太厉害的,不容易啊,整理这么多,还这么详细,感谢分享啊
点赞 回复 分享
发布于 2022-04-10 21:53

相关推荐

jack_miller:我给我们导员说我不在这里转正,可能没三方签了。导员说没事学校催的时候帮我想办法应付一下
点赞 评论 收藏
分享
10-28 14:42
门头沟学院 Java
watermelon1124:因为嵌入式炸了
点赞 评论 收藏
分享
4 19 评论
分享
牛客网
牛客企业服务