Redis的经典面试题

1.你用过Redis 吗? Redis是什么? 

有用过redis 。Redis是基于内存的数据结构存储服务器。常用作数据库、缓存、消息队列。是目前最主流的nosql数据库之一。

2.我们常用的数据类型有哪些?你能说说分别在那些场景中使用?

reids中常用的数据类型有 String、list、set、hash、zset

(1)String类型:

redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据,byte字节等,也就是string类型能存储任何数据,你可以用它存储序列化后的用户对象,json化的对象,甚至图片,视频等

        使用场景:

可以用来做计数器、常用命令:set、get、incr、decr、incrby、decrby

(2)list类型:

可以存储一个字符串列表,按照数据加入的顺序排列,它非常方便的在列表头部和尾部添加元素。列表类型内部使用双向链表实现,所以向列表两端添加元素非常快, 但是如果尝试访问一个非常大的列表的中间元素,则速度会很慢

        使用场景:

类似时间点的访问。每个时间点的重大事件存入list形成时间轴;例如好友动态、评论等;

常用命令:lpush、rpush;

记住最近的操作:往list列表头部放元素,取头部的几个元素即为最近记录,常用命令:lpush、ltrim

(3)Set类型:

无顺序的字符串集合,集合中每个元素都是不同的不允许有重复数据。其内部存储是一个值为空的散列表,所以增删速度也很快。

        使用场景:

可以使用set做抽奖,把名单放入set、set不会重复,随机弹出元素。

(4)zset类型:

与set类似,也是一个无重复数据的集合,不同的是zset的每个元素会关联一个分数,这个分数用于对集合元素进行排序;(从小到达)zset中的元素是唯一的,但是每个元素的分数是可以重复的。

        使用场景:

实时的数据排行,每次数据的更新会更新分数

(5)hash类型:

是一个string类型的field和value的映射表

使用场景:

hash用于存储对象、例如关注数、粉丝数等。

注:一个对象中的某一个属性是唯一的,对同一个属性进行操作会进行覆盖操作。

3.Redis可以设置过期吗?你能说说Redis的过期清理原理吗?

在Redis中提供了Expire命令设置一个键的过期时间,到期以后Redis会自动删除它。

(1)Redis删除过期key的方法主要有两种:

        1.消极方法:

        在key被访问时如果发现它已经失效,那么就删除它。

        2.积极方法:

        周期性地从设置了过期时间的key中选择一部分过期的key删除,对于那些从未被查询的key,即便它们已经过期,消极方式也无法清除,因此Redis会周期性地随机测试一些key,已过期的key将会被删掉。

扩充:Redis积极方法删除过期key

Redis每秒会进行10次操作,具体的流程:

1. 随机测试 20 个带有过期信息的key;

2. 删除其中已经过期的key;

3. 如果超过25%的key被删除,则重复执行步骤1;

这是一个简单的概率算法,基于假设我们随机抽取的key代表了全部的key空间。

4.缓存 有什么作用

缓存是解决系统性能问题的利器,通过缓存技术来降低后端服务器压力,提升系统整体性能,缩短响应时间。特别是在大流量高并发场景下,缓存可以说是解决大流量高并发,优化系统性能首要考虑的因素。

5.什么是缓存穿透?如何解决?

缓存穿透是指大量不存在的key请求,由于缓存没有,便开始查询数据库,但数据库也没有查到数据库比如一些恶意攻击,爬虫等造成大量空命中。

解决方案:

方案一:

缓存空结果,对数据库查询不存在的数据仍然记录在缓存中缓存一条数据,比如缓存一条空值unknow,这样能有效减少查询数据库的次数。

 方案二:布隆过滤器

布隆过滤器是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

6.什么是缓存击穿?如何解决?

高并发条件下,对于热点数据访问,在某个时刻数据失效了;或者刚开始时缓存中还没有对数据进行缓存,所有请求都被发送到数据库去查询,数据库被压垮。

解决方案:

方案一:

使用分布式锁,就是在访问数据库之前都先请求分布式锁,获得锁的那个线程才有资格去访问数据库,其他线程必须等待。整体思路:拿到锁的线程查询出来的数据结果会重新放入redis缓存,然后释放锁,这样当后面排队的人拿到锁的时候,再次进行缓存判断,这时缓存已经有数据了,那么直接不再对数据库进行操作。

方案二:

对即将过期的数据主动刷新,比如起一个后台定时任务轮询,主动跟新缓存数据,保证缓存不会全部失效。

7.什么是缓存雪崩?如何和解决?

缓存雪崩是指比如我们给所有的数据设置了同样的过期时间,然后在某一个历史性时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都落到数据库,数据库被压垮。或者是缓存服务器发生故障,导致所有的请求都落入到数据库,数据库被压垮。

解决 方案:

事前:redis要高可用(搭建集群或者哨兵),避免redis不可用。

事中:本地ehcache缓存(mybatis二级缓存)+限流&降级,避免数据库被压垮。

事后:redis持久化,快速恢复缓存数据。

8.如何解决数据库与缓存的双写不一致问题?

1.为缓存设置过期时间

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

一般情况下,我们是这样使用缓存:

缓存的读取操作:先判断是否有缓存,有缓存直接返回给前端,无缓存,从数据库查询数据,如果数据库查询结果为空,则直接返回给前端,如果数据库查询结果不为空,则将数据写入缓存再返回给前端;

缓存的更新操作:可以分为:

(1)先更新数据库,再更新缓存;(不行)

(2)先更新缓存,再更新数据库;(不行)

(3)先更新数据库,再删除缓存;

(4)先删除缓存,再更新数据库;(国内推荐

2.使用延迟双删策略

扩展:缓存的更新

一般情况下,我们是这样使用缓存:

缓存的读取操作:先判断是否有缓存,有缓存直接返回给前端,无缓存,从数据库查询数据,如果数据库查询结果为空,则直接返回给前端,如果数据库查询结果不为空,则将数据写入缓存再返回给前端;

缓存的更新操作:可以分为:

(1)先更新数据库,再更新缓存;(不行)

(2)先更新缓存,再更新数据库;(不行)(不存在)

(3)先更新数据库,再删除缓存;

(4)先删除缓存,再更新数据库;(国内推荐)

先更新数据库,再更新缓存

(1)先更新数据库,再更新缓存(不行)

从线程安全角度,此方案是有问题的:

同时有请求A和请求B进行更新操作,那么会出现

(1)线程A更新了数据库

(2)线程B更新了数据库

(3)线程B更新了缓存

(4)线程A更新了缓存

这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存,这就导致了脏数据,因此不考虑。

从业务场景角度,此方案也是有问题的:

(1)如果是一个写数据比较多,而读数据比较少的业务场景,采用这种方案就会导致数据压根不怎么去读,缓存却被频繁地更新,很浪费性能。

(2)如果写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算后再写入缓存,那么每次写入数据库后,都需要计算写入缓存的值,也浪费性能,显然删除缓存更加适合。

先更新数据库,再删除缓存

先把数据存到数据库中,成功后,再让缓存失效。Facebook他们有一次演讲中提到,他们用的是先更新数据库,再删缓存的策略。

但在国内,通过同行的仔细推敲,发现该方案也存在的问题:

同时有请求A和请求B,A进行查询请求,B进行更新请求,那么会出现

(1)缓存刚好失效;

(2)请求A查询数据库,得一个旧值;

(3)请求B将新值写入数据库;

(4)请求B删除缓存;

(5)请求A将查到的旧值写入缓存。

一般情况下,这种情况难以出现,因为数据库读操作的速度远快于写操作的速度,因此步骤3的耗时比步骤2更长,所以步骤4会在步骤5的后面执行。

如果万一发生此情况:

1、给缓存要设有效时间,让脏数据可以过期。

2、使用异步延时删除策略(延时双删策略),即是,在写操作完成后,开启一个异步线程,

先删除缓存,再更新数据库

该方案会导致不一致,比如同时有一个请求A进行更新操作,另一个请求B进行查询操作。

(1)请求A进行写操作,先删除缓存

(2)请求B查询发现缓存不存在

(3)请求B去数据库查询得到旧值

(4)请求B将旧值写入缓存

(5)请求A将新值写入数据库

那么将导致数据不一致的,如果不给缓存设置过期时间,该数据将永远都是脏数据。

解决方案:

1、给缓存要设有效时间,让脏数据可以过期。

2、使用异步延时删除策略(延时双删策略),即是,在写操作完成后,开启一个异步线程,等待一段时间后再进行删除操作,这样能保证读请求完成以后。

9.Redis中使用lua脚本的好处是什么?

1、原子操作,redis会将整个脚本作为一个整体执行,中间不会被其他命令插入,换句话说,编写脚本的过程中无需担心会出现插队、竞争等条件。

2、减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行。

扩充:在使用redis的时候面临的问题

原子性问题

Redis虽然是单线程的,但是仍然可能会出现线程安全问题,当然这个线程安全问题不是来源于Redis服务器内部,而是多个客户端去操作Redis的时候,多个客户端的操作就相当于同一个进程下的多个线程,如果多个客户端之间没有做好同步策略,就会产生数据不一致的问题。比如:我想按照顺序执行几个Redis命令,但是多个客户端的命令之间没有做请求同步,导致实际执行顺序与预想的不一致,最终的结果也就无法满足原子性。

执行效率问题

Redis由于是基于内存的数据库,它本身的吞吐量是非常高的,那么影响Redis吞吐量的一个重要因素是网络,我们用redis实现某些特定功能,很可能需要用多个命令、多个数据类型、多个步骤一起才能完成功能,那么在实现这个功能的过程中,就会产生多次对Redis的网络访问,多次向Redis发送和获取数据,这种多次网络请求对性能影响是非常大的。如果我们能将一系列的对redis的操作变成一次操作,那会大大地减少网络请求,提升效率。

10.Redis的内存淘汰策略你知道那些?

noeviction: 默认策略,不淘汰,如果内存已满,添加数据时报错。

allkeys-lru: 在所有键中,选取最近最少使用的数据淘汰。

volatile-lru: 在设置了过期时间的所有键中,选取最近最少使用的数据淘汰。

allkeys-lfu: 在所有键中,选取最近最不常访问的数据淘汰。

volatile-lfu: 在设置了过期时间的所有键中,选取最近最不常访问的数据淘汰。

allkeys-random: 在所有键中,随机淘汰;。

volatile-random: 在设置了过期时间的所有键,随机淘汰。

volatile-ttl: 在设置了过期时间的所有键,存活时间最短的数据淘汰。

11.Redis的持久化有那些方法?有什么区别?

RDB是默认开启的,是指在指定的时间间隔内将内存中的数据集快照写入磁盘,数据恢复时将快照文件直接再读到内存。

扩充:RDB方式的内部实现原理

RDB方式的数据持久化,仅需在redis.conf文件中配置即可

配置文件搜索 SNAPSHOTTING 部分,

配置格式:save <seconds> <changes>

save 900 1

save 300 10

save 60 10000

当满足条件时,Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,等到持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程是不进行任何关于持久化相关的IO操作的,这就确保了不影响主进程,保证Redis极高的性能;

如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效,RDB的缺点是最后一次持久化后的数据可能丢失。

AOF是指Redis每次接收到一条改变数据的命令时,它将把该命令写到一个AOF文件中(只记录写操作,不记录读操作),当Redis重启时,它通过执行AOF文件中所有的命令来恢复数据。这种方式的持久化让redis的数据不会丢失;AOF需要手动开启,AOF可以将Redis执行的每一条写命令追加到硬盘文件中,这一过程会降低Redis的性能,但大部分情况下这个影响是能够接受的,另外使用较快的硬盘(固态硬盘)可以提高AOF的性能

扩充:AOF的内部原理

AOF方式的数据持久化,仅需在redis.conf文件中配置即可,搜索APPEND ONLY MODE;

配置文件 appendonly no 默认是no,改成yes即开启aof持久化;

AOF的相关配置选项

appendonly

默认是no,改成yes即开启了aof持久化;

appendfilename

指定AOF文件名,默认文件名为appendonly.aof;(该文件要Redis自己启动创建,

重写是怎么回事:

AOF 文件会在操作过程中变得越来越大。比如,如果你做一百次加法计算,最后你只会在数据库里面得到最终的数值(k 100),但是在你的 AOF 里面会存在 100 次记录,其中 99 条记录对最终的结果是无用的;但 Redis 支持在不影响服务的前提下在后台重构 AOF 文件,让文件得以整理变小;

AOF的重写原理

重写时,主进程会fork一个子进程出来进行AOF重写,这个重写过程并不是基于原有的aof文件来做的,而是有点类似于快照的方式,全量遍历内存中的所有数据,然后逐个序列到aof文件中。

在fork子进程这个过程中,服务端仍然可以对外提供服务,在重写aof文件的过程中,主进程的数据更新操作,会缓存到aof_rewrite_buf中,也就是单独开辟一块缓存来存储重写期间收到的操作命令,当子进程重写完以后再把缓存中的数据追加到新的aof文件。

当所有的数据全部追加到新的aof文件中后,把新的aof文件重命名,此后所有的操作都会被写入新的aof文件。如果在rewrite过程中出现故障,不会影响原来aof文件的正常工作,只有当rewrite完成后才会切换覆盖原来那个文件,因此这个 rewrite过程是比较稳定可靠的。

12.Redis的集群模式有那几种

Redis集群有哨兵模式和分片集群模式

1.哨兵模式的本质上是主从复制,优点是搭建简单,能满足大部分时候的需求,缺点是每一台redis服务器都拥有相同的缓存数据,会浪费内存空间。

2.分片集群模式,提出哈希槽的概念,来实现数据分片,在redis内部哈希槽的个数是16384个,这个数量是不可变的。每一个哈希槽分区为一个独立的集群,当用户存储数据的时候,先进行哈希槽计算,最终把数据存到不同的哈希槽分区。避免了浪费内存空间的情况,而且可以很好的横向扩展。缺点是搭建比较麻烦,需要的服务器资源也比较多。

扩充:哨兵模式原理及搭建

哨兵模式用来监视Redis的主从服务器,它会不断检查Master和Slave是否正常;如果哨兵挂了,就无法监控,所以需要多个哨兵,组成Sentinel网络,监控同一个Master的各个Sentinel哨兵会相互通信,组成一个分布式的哨兵网络,互相交换彼此关于被监控redis服务器的信息,当一个哨兵认为被监控的redis服务器出现故障时,它会向网络中的其它哨兵进行确认,判断该服务器是否真的已故障,如果故障的redis服务器为主服务器,那么哨兵网络将对故障的主redis服务器进行自动故障转移,通过将故障的主redis服务器下的某个从服务器提升为新的主服务器,并让其它从服务器转移到新的主服务器下,以此来让整个主从模式重新回到正常状态;待出现故障的旧主服务器重新启动上线时,哨兵会让它变成一个从redis服务器,并挂到新的主redis服务器下。

如何搭建哨兵:

由上图可知,搭建哨兵模式分为两个部分,第一是我们需要搭建redis本身的主从复制服务器集群,第二是搭建一个哨兵网络集群来负责监控redis集群

####搭建主从复制集群####

实现Redis的主从复制,只需要修改Redis的主配置文件redis.conf即可,配置如下:(注意:我们现在是在一台Linux机器上配置的,实际项目中我们是在多台Linux进行配置,当然配置是完全一样的,只是多台Linux上端口号不需要改了)

单一主机演示配置:

主redis配置:

include /usr/local/redis-5.0.5/redis.conf

daemonize yes

port 6380

pidfile /var/run/redis_6380.pid

logfile 6380.log

dbfilename dump6380.rdb

从服务器配置:

include /usr/local/redis-5.0.5/redis.conf

daemonize yes

port 6381

pidfile /var/run/redis_6381.pid

logfile 6381.log

dbfilename dump6381.rdb

slaveof 192.168.87.129 6380

masterauth 123456

多台主机:

只需要修改每台主机的config文件即可,包含端口,后台运行,密码,绑定ip等等。

其中从主机需要添加slaveof属性

验证主从复制

  1. 依次启动主从redis;

./redis-server ../redis6380.conf

./redis-server ../redis6381.conf

./redis-server ../redis6382.conf

2、向主redis写入数据;

3、查看从redis是否能够获取到主redis写入的数据;

注:配置主从模式涉及到的相关命令

进入客户端需指定端口(否则默认是进入6379端口):./redis-cli -p 6380

当没有配置主从关系时,redis启动后默认都是主master;

通过命令 info replication 查看redis服务器所处角色;

####搭建哨兵网络####

1.在redis主目录下复制三份sentinel.conf文件:

sentinel26380.conf

sentinel26381.conf

sentinel26382.conf

2.三份sentinel配置文件修改(两处):(这里也是以单台主机为例,多台主机的话,直接在原有sentinel文件上修改即可)

1、修改 port 26380、 port 26381、 port 26382 (Sentinel默认端口号为26379)

2、修改 sentinel monitor mymaster 192.168.10.128 6380 2

格式:Sentinel monitor <name> <masterIP> <masterPort> <Quorum投票数>

Sentinel会根据Master的配置自动发现Master下的Slave;

3、如果redis有密码,需要配置master密码;

sentinel auth-pass mymaster 123456

4、如果你需要在远程的机器上去连接哨兵,则需要修改:

protected-mode no

3.启动Sentinel

redis安装时make编译后就产生了redis-sentinel的可执行程序文件,可以在一个redis中运行多个sentinel进程;

启动三个Sentinel哨兵进程,将创建三个监视主服务器的Sentinel实例,执行如下命令:

/redis-sentinel  ../sentinel26380.conf &

./redis-sentinel  ../sentinel26381.conf &

./redis-sentinel  ../sentinel26382.conf &

&号表示后台启动

4.验证Sentinel哨兵模式

1、将主从模式的redis中的主redis服务器关闭;

2、观察从服务器中是否有一台被提升为主服务;

哨兵故障转移默认是30秒后开始,可以修改配置:

# Default is 30 seconds.

sentinel down-after-milliseconds mymaster 30000

扩充:分片集群模式:

一个Redis分片集群由多个Redis节点构成,不同节点组的redis数据没有交集,也就是每个节点组对应数据的一个分片,节点组内部分为主备两类节点,对应master和slave节点,两者数据准实时一致,通过异步化的主备复制机制来保证。一个节点组有且只有一个master节点,同时可以有0(没有)到多个slave节点,在这个节点组中只有master节点对用户提供些服务,Redis 分片集群提出哈希槽的概念,来实现数据分片这种结构很容易添加或者删除节点,比如:如果我想新添加一个节点D, 我需要从节点 A, B, C中得部分槽到D上;如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可;由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态,并且达到了扩容的目的。

注意:redis分片集群的哈希槽固定为16384个,这个数量是不可变的。

13.Redis中查询大量的key要怎么办?

可以使用scan命令

扩充:为什么不能使用keys命令

如果Redis中有1亿个key,其中有20w个key是以某个固定前缀开头的,如何将它们全部查出来? user:id:

我们知道使用keys指令可以列出指定模式的所有key;

但是如果这个redis是线上服务正在提供业务服务,那使用keys指令会有什么问题?

Redis关键的一个特性是redis是单线程,当 KEYS 命令被用于处理一个大的数据库时, 它们可能会阻塞服务器达数秒之久,线上服务会停顿,直到指令执行完毕,服务才能恢复。

这个时候可以使用scan命令,scan指令可以无阻塞的提取出指定模式的key列表,它支持增量式迭代, 它们每次执行都只会返回少量元素, 所以这些命令可以用于生产环境, 而不会出现像 KEYS 命令阻塞卡顿。

命令如下:

SCAN 0 match user:id:*     此命令会返回2种类型的数据,第1种是一个游标值,第2种是具体key的名称,返回的数量是有限的,我们可以用游标值做为下一次查询的起始点,当全部数据都查询完毕后,游标值会变成0

14.Redis为什么那么快?

1.Redis是基于内存操作。

2.Redis是单线程,省去线程切换、锁竞争的开销。

3.Redis非阻塞IO模型。(NIO)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值