Redis面试题(2022)

目录

Redis常见问题(面试题)

常见问题

1、Redis是单线程还是多线程,为什么那么快

2、redis怎么实现持久化

3、缓存的三大问题及解决方案

4、先删后写还是先写后删

5、如何实现redis的高并发

6、redis的应用场景

常用命令

数据结构

1、Redis 有哪些数据结构?

2、String 类型的底层实现?

3、说说 SDS 带来的好处?

4、Redis 实现的链表有哪些特性?

数据存储

1、Redis 的数据过期策略是什么?

2、持久化文件对过期策略的处理?

3、Redis 有哪些内存淘汰机制?

4、缓存污染

Pipeline

1、什么是 Redis 的 Pipeline?

2、原生批命令 (mset, mget) 与 Pipeline 区别?

多线程

1、Redis 6 之前真的是单线程吗?

2、Redis 6 之前为什么使用单线程?

3、Redis 6 为何引入多线程?

4、多线程如何开启以及配置?

5、Redis 多线程的实现机制?

6、多线程是否会导致线程安全问题?

集群

1、Redis 集群搭建有几种模式?

布隆过滤器

1、什么是布隆过滤器

2、布隆过滤器的使用场景

3.1 数据结构

3.2 空间计算


Redis常见问题(面试题)

常见问题

1、Redis是单线程还是多线程,为什么那么快

虽然redis非常的快但是redis使用的还是单线程,速度快的原因主要有以下几点。

  • 基于内存

  • IO多路复用技术

  • 高效的数据结构

  • 单线程减少上下文切换,同时保证原子性;

至于为什么使用单线程

因为redis是基于内存的,cup不会成为redis的瓶颈。若使用多线程则CPU需要在不同的线程之间来回切换,上下文开销比较大。影响redis最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。

2、redis怎么实现持久化

主要使用的是两种策略RDBAOF

RDB

RDB持久化是:指在一定时间间隔内redis操作满足一定的条件就将内存中的数据快照写入磁盘,这是redis默认的持久化方式。就是将内存中的数据以快照的方式写入到二进制文件中,默认的文件名是dump.rdb

RDB支持同步(save)和后台同步(bgsave)自动触发 三种触发方式。

save 和 bgsave需要手动输入, 自动触发在配置文件中自己设置触发条件。

优点

  • RDB文件紧凑,全量备份,非常适合用于备份和灾难恢复

  • 生成RDB文件支持异步处理,主进程不需要进行任何IO操作

  • RDB在恢复大数据集时速度要比AOF快

缺点

RDB是全量备份,储存的是内存数据的二进制序列化形式储存非常紧凑。而且在快照持久化期间修改的数据不会被保存,可能存在丢失数据的情况。

AOF

AOF相比于RDB更加的高效,简单的说就是将收到的对数据进行修改的命令追加到文件中。

随着AOF不断地记录写操作日志,因为所有的操作都会记录,所以必定会出现一些无用的日志。大量无用的日志会让AOF文件过大,也会让数据恢复的时间过长。不过Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集。

AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行:

auto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb

默认重写策略是:

Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite。同时如果增长的大小没有达到64mb,则不会进行rewrite。

AOF 也有三种触发方式

1)每修改同步 always

2)每秒同步 everysec

3)不同no:从不同步 同步时间由os决定,不是不进行持久化。

优点

  • AOF 可以更好的保护数据不丢失,一般 AOF 隔 1 秒通过一个后台线程执行一次 fsync 操作

  • AOF 日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损

  • AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写

  • AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复

缺点

对于相同的数据来说AOF日志文件通常比RDB数据快照文件更大。

AOF开启后,支持的写 QPS 会比RDB支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的

RDB 和 AOF 该如何选择

通常情况我们是二者结合使用的。 

Redis面试题(2022)_第1张图片

3、缓存的三大问题及解决方案

缓存穿透

查询的数据不存在

解决方法

  • 1)缓存空值

  • 2)key值校验,例如布隆过滤器ref

缓存击穿

热门的key值在正需要的时候,突然失效了,导致同一时间,大量请求直逼数据库,数据库瞬间崩了。

解决方法

  • 1)预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长。

  • 2)实时调整:现场监控哪些数据热门,实时调整key的过期时长(Apache Druid)。

  • 3)使用锁: 就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db

缓存雪崩

不太热门的key值,在同一时间,集体失效了。导致mysql的压力瞬间增大,mysql崩了。针对的是多个key值。

解决方法

  • 1、redis中的key值,设置不同的过期时间,假如是批量设置,可以编写一个随机函数,让其在1~5分钟过期即可。

  • 2、添加多级缓存:nginx缓存 + redis缓存 +其他缓存(ehcache等)

  • 3、还可以使用锁或者队列的方式。

  • 4、热点数据永不过期

4、先删后写还是先写后删

先删缓存后写 DB 产生脏数据的概率较大(若出现脏数据,则意味着再不更新的情况下,查询得到的数据均为旧的数据)。

比如两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。

先写 DB 再删缓存 产生脏数据的概率较小,但是会出现一致性的问题;若更新操作的时候,同时进行查询操作并命中,则查询得到的数据是旧的数据。但是不会影响后面的查询。

比如一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后之前的那个读操作再把老的数据放进去,所以会造成脏数据。

解决方案

1)缓存设置过期时间,实现最终一致性;

2)使用 Cannel 等中间件监听 binlog 进行异步更新;

3)通过 2PC 或 Paxos 协议保证一致性。

5、如何实现redis的高并发

redis主从复制

  • 实现读写分离,减轻主节点压力

  • 数据同步,达到类似备份的目的

缺点是当主节点挂了后要手动切换主节点。

哨兵模式

解决主从模式下,master节点挂掉以后,从节点无法自动升级为主节点的问题。

6、redis的应用场景

7、有哪些应用场景? Redis 在互联网产品中使用的场景实在是太多太多,这里分别对 Redis 几种数据类型做了整理:

1)String:缓存、限流、分布式锁、计数器、分布式 Session 等。

2)Hash:用户信息、用户主页访问量、组合查询等。

3)List:简单队列、关注列表时间轴。

4)Set:赞、踩、标签等。

5)ZSet:排行榜、好友关系链表。

常用命令

连接终端

redis-cli -h 127.0.0.1 -p 6379

keys *  # 获取所有的key
select 0  # 选择第一个库
move myString 1  # 将当前的数据库key移动到某个数据库,目标库有,则不能移动
flush db  # 清除指定库
randomkey  # 随机key
type key  # 类型
set key1 value1   # 设置key
get key1  # 获取key
del key1  # 删除key
exists key  # 判断是否存在key
expire key 10  # 10s 过期
pexpire key  # 1000 毫秒
persist key  # 删除过期时间
ttl key     #查看是否过期

#订阅与发布一般由别的软件做。
subscribe chat1 # 订阅频道
publish chat1 "hell0 ni hao"  # 发布消息
pubsub channels  # 查看频道
pubsub numsub chat1  # 查看某个频道的订阅者数量
unsubscrible chat1  # 退订指定频道 或 punsubscribe java.*
psubscribe java.*  # 订阅一组频道

数据结构

1、Redis 有哪些数据结构?

  • 字符串 String

  • 字典 Hash

  • 列表 List

  • 集合 Set

  • 有序集合 Zset

2、String 类型的底层实现?

为了将性能优化到极致,Redis 作者为每种数据结构提供了不同的实现方式,以适应特定应用场景。以最常用的 String 为例,其底层实现就可以分为 int、embstr 以及 raw 这三种类型。这些特定的底层实现在 Redis 中被称为编码(Encoding),可以通过 OBJECT ENCODING [key] 命令查看。

Redis 中所有的 key 都是字符串,这些字符串是通过一个名为简单动态字符串(SDS) 的抽象数据类型实现的。

struct sdshdr{
     //记录buf数组中已使用字节的数量
     //等于 SDS 保存字符串的长度
     int len;
     //记录 buf 数组中未使用字节的数量
     int free;
     //字节数组,用于保存字符串
     char buf[];
}

3、说说 SDS 带来的好处?

我们知道 Redis 是使用 C 语言写的,那么相比使用 C 语言中的字符串(即以空字符 \0 结尾的字符数组),自己实现一个 SDS 的好处是什么?

1)常数复杂度获取字符串长度

由于 len 属性的存在,我们获取 SDS 字符串的长度只需要读取 len 属性,时间复杂度为 O(1)。

2)杜绝缓冲区溢出

3)减少修改字符串的内存重新分配次数

4)二进制安全

5)兼容部分 C 字符串函数

一般来说,SDS 除了保存数据库中的字符串值以外,还可以作为缓冲区(Buffer):包括 AOF 模块中的 AOF 缓冲区以及客户端状态中的输入缓冲区。

4、Redis 实现的链表有哪些特性?

链表是一种常用的数据结构,C 语言内部是没有内置这种数据结构的实现,所以 Redis 自己构建了链表的实现。

1)双端:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为 O(1)。

2)无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结束。

3)带长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。

4)多态:链表节点使用指针来保存节点值,可以保存各种不同类型的值。

数据存储

1、Redis 的数据过期策略是什么?

在回答词问题之前,首先需要回答另一个问题,就是如何设置 Redis 中数据的过期时间?

  • 1)expire key time (以秒为单位)-这是最常用的方式

  • 2)setex(String key, int seconds, String value) --字符串独有的方式

除了字符串有自己独有的设置过期时间的方式之外,其它都要依靠expire来设置过期时间,如果没有设置过去时间就是永不过期,如果设置了过期时间就使用persist key让缓存永不过期

常见的过期策略

1)定时删除

在设置 key 的过期时间的同时,为该 key 创建一个定时器,让定时器在 key 的过期时间来临时,对 key 进行删除。

2)惰性删除

被查询的时候发现过期时立刻删除。

3)定期删除

每隔一段时间执行一次删除(在 redis.conf 配置文件设置,1s 刷新的频率)过期 key 操作。

Redis采用的过期策略

redis 采用定期删除+惰性删除的方式

惰性删除的流程

1)在进行get或setnx操作的时候检查key是否过期

2)若过期删除key,然后执行相应的操作

3)若没过期,直接执行相应操作。

定期删除的流程

其核心是对指定个数个库的每一个库随机删除小于等于指定个数个过期 key:

1)历每个数据库(就是 redis.conf 中配置的 “database” 数量,默认为16);

2)检查当前库中的指定个数个 key (默认是每个库检查 20 个,相当于该循环执行 20 次):

2.1)如果当前库中没有一个 key 设置了过期时间,直接执行下一个库的遍历;

2.2)随机获取一个设置了过期时间的 key,检查是否过期,如果过期则删除;

2.3)判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。

2、持久化文件对过期策略的处理?

过期 key 是不会写入 RDB 和 AOF 文件,同时数据恢复时也会做过期验证。

3、Redis 有哪些内存淘汰机制?

在内存不足时开启淘汰机制

volatile(设置过期时间的数据集)

  • 1)volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。

  • 2)volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。

  • 3)volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。

  • 4)volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。

allkeys(所有数据集)

  • 5)allkeys-lru:从数据集中挑选最近最少使用的数据淘汰

  • 6)allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。

  • 7)allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction

  • 8)no-enviction(驱逐):禁止驱逐数据,这也是默认策略。当使用的内存空间超过 maxmemory 值时,返回错误;

4、缓存污染

在一些场景下,有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用内存空间。这种情况,就是「缓存污染」。

解决方案

那就是得把不会再被访问的数据筛选出来并淘汰掉。这样就不用等到缓存被写满以后,再逐一淘汰旧数据之后,才能写入新数据了。

Pipeline

1、什么是 Redis 的 Pipeline?

在出现 Pipeline 之前,我们梳理一下 Redis 客户端执行一条命令需要经过哪些步骤:发送命令-〉命令排队-〉命令执行-〉返回结果

这个过程称为 Round trip time(简称RTT, 往返时间),mget 和 mset 有效节约了 RTT,但大部分命令(如 hgetall 并没有 mhgetall)不支持批量操作,需要消耗 N 次 RTT ,这个时候需要 pipeline 来解决这个问题。

Redis面试题(2022)_第2张图片

2、原生批命令 (mset, mget) 与 Pipeline 区别?

  • 1)原生批命令是原子性的,而 pipeline 是非原子操作。

  • 2)原生批命令一命令多个 key, 但 pipeline 支持多命令(存在事务),非原子性。

  • 3)原生批命令是服务端实现,而 pipeline 需要服务端与客户端共同完成。

多线程

1、Redis 6 之前真的是单线程吗?

Redis 在处理客户端的请求时,包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的单线程。但如果严格来讲从 Redis 4 之后并不是单线程,除了主线程外,它也有后台线程在处理一些较为缓慢的操作,例如清理脏数据、无用连接的释放、大 key 的删除等等。

2、Redis 6 之前为什么使用单线程?

使用了单线程后,可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。

同时 Redis 通过 AE 事件模型以及 IO 多路复用等技术,即使单线程处理性能也非常高,因此没有必要使用多线程。单线程机制使得 Redis 内部实现的复杂度大大降低,Hash 的惰性 Rehash、Lpush 等等 “线程不安全” 的命令都可以无锁进行。

3、Redis 6 为何引入多线程?

随着目前行业内越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的 QPS。常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,比如:

1)要管理的 Redis 服务器太多,维护代价大;

2)某些适用于单个 Redis 服务器的命令不适用于数据分区;

3)数据分区无法解决热点读/写问题;

4)数据偏斜,重新分配和放大/缩小变得更加复杂等等。

从 Redis 自身角度来说,因为读写网络的 read/write 系统调用占用了 Redis 执行期间大部分 CPU 时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:

1)提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式;

2)使用多线程充分利用多核,典型的实现比如 Memcached。

协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,Redis 支持多线程主要就是两个原因:

可以充分利用服务器 CPU 资源,目前主线程只能利用一个核; 多线程任务可以分摊 Redis 同步 IO 读写负荷。

4、多线程如何开启以及配置?

Redis 6 的多线程默认是禁用的,只使用主线程。如需开启需要修改 redis.conf 配置文件中的 io-threads-do-reads yes。

开启多线程后,还需要设置线程数,否则是不生效的。同样修改 redis.conf 文件中的 io-threads [n] 配置。

关于线程数的设置,官方有一个建议:4 核的机器建议设置为 2 或 3 个线程,8 核的建议设置为 6 个线程,线程数一定要小于机器核数。还需要注意的是,线程数并不是越大越好,官方认为超过了 8 个基本就没什么意义了。

5、Redis 多线程的实现机制?

Redis面试题(2022)_第3张图片

大致流程如下: 1)主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列;

2)主线程处理完读事件之后,通过 RR(Round Robin) 将这些连接分配给这些 IO 线程;

3)主线程阻塞等待 IO 线程读取 socket 完毕;

4)主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行;

5)主线程阻塞等待 IO 线程将数据回写 socket 完毕;

6)解除绑定,清空等待队列。

该设计的特点:

1)IO 线程要么同时在读 socket,要么同时在写,不会同时读或写。

2)IO 线程只负责读写 socket 解析命令,不负责命令处理。

6、多线程是否会导致线程安全问题?

从上面的实现机制可以看出,Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。所以我们不需要去考虑控制 key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。

集群

1、Redis 集群搭建有几种模式?

主从模式

和 MySQL 需要主从复制的原因一样,Redis 虽然读写速度非常快,但是也会产生性能瓶颈,特别是在读压力上,为了分担压力,Redis 支持主从复制。Redis 的主从结构一主一从,一主多从或级联结构,复制类型可以根据是否是全量而分为全量同步和增量同步。

主节点中有一个缓冲区,在同步命令的时候不仅向从节点发送数据也向缓冲区中发送一份而且数据中,当掉线的从节点上线的时候就能从主节点的缓冲区取数据。

在同步的数据中有一个游标叫做复制偏移量,从0开始的Long类型,当数据同步的时候一起更新游标,当掉线的从节点上线的时候就可以从根据复制偏移量拉取数据。

缺点当主节点挂掉的时候需要程序员手动切换主节点

使用:slaveof NO ONE 命令可以让从节点顶上去

Redis面试题(2022)_第4张图片

哨兵模式

在主从复制实现之后,如果想对 master 进行监控,Redis 提供了一种哨兵机制,哨兵的含义就是监控 Redis 系统的运行状态,通过投票机制,从 slave 中选举出新的 master 以保证集群正常运行。

还可以启用多个哨兵进行监控以保证集群足够稳健,这种情况下,哨兵不仅监控主从服务,哨兵之间也会相互监控。

哨兵(sentine)会每隔10秒 sentine就会通过INFO命令问候主节点,获取主节点有哪些从节点

哨兵每隔1秒会使用PING命令询问所有节点,如果在规定时间里主节点没有收到回复,这个哨兵就主观判断这个主节点下线。如果多个哨兵判断客观下线,就开始故障转移。

故障转移

  • 1)选出新的主节点

    • 硬件配置越高的优先级越高

    • 偏移量越大的数据越全优先级越高

    • 与主节点断开时间越短优先级越高

  • 2)让别的从节与新的主节点同步数据

  • 3)把原来的主节点改为从节点

缺点

  • 主从服务器的数据要经常进行主从复制,这样会造成性能下降

  • 当主服务器宕机后,从服务器切换成主服务器的那段时间,服务是不可用的

Cluster 集群模式

Redis面试题(2022)_第5张图片

布隆过滤器

缓存被穿透了怎么办啊

那就站在布隆后面

1、什么是布隆过滤器

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

上面这句介绍比较全面的描述了什么是布隆过滤器,如果还是不太好理解的话,就可以把布隆过滤器理解为一个set集合,我们可以通过add往里面添加元素,通过contains来判断是否包含某个元素。由于本文讲述布隆过滤器时会结合Redis来讲解,因此类比为Redis中的Set数据结构会比较好理解,而且Redis中的布隆过滤器使用的指令与Set集合非常类似(后续会讲到)。

学习布隆过滤器之前有必要先聊下它的优缺点,因为好的东西我们才想要嘛! 布隆过滤器的优点:

时间复杂度低,增加和查询元素的时间复杂为O(N),(N为哈希函数的个数,通常情况比较小) 保密性强,布隆过滤器不存储元素本身 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的(相比其他数据结构如Set集合) 布隆过滤器的缺点:

有点一定的误判率,但是可以通过调整参数来降低 无法获取元素本身 很难删除元素

2、布隆过滤器的使用场景

布隆过滤器可以告诉我们 “某样东西一定不存在或者可能存在”,也就是说布隆过滤器说这个数不存在则一定不存,布隆过滤器说这个数存在可能不存在(误判,后续会讲),**利用这个判断是否存在的特点可以做很多有趣的事情。

解决Redis缓存穿透问题(面试重点) 邮件过滤,使用布隆过滤器来做邮件黑名单过滤 对爬虫网址进行过滤,爬过的不再爬 解决新闻推荐过的不再推荐(类似抖音刷过的往下滑动不再刷到) HBase\RocksDB\LevelDB等数据库内置布隆过滤器,用于判断数据是否存在,可以减少数据库的IO请求 3、布隆过滤器的原理

3.1 数据结构

布隆过滤器它实际上是一个很长的二进制向量和一系列随机映射函数。以Redis中的布隆过滤器实现为例,Redis中的布隆过滤器底层是一个大型位数组(二进制数组)+多个无偏hash函数。 一个大型位数组(二进制数组):

Redis面试题(2022)_第6张图片

多个无偏hash函数: 无偏hash函数就是能把元素的hash值计算的比较均匀的hash函数,能使得计算后的元素下标比较均匀的映射到位数组中。

如下就是一个简单的布隆过滤器示意图,其中k1、k2代表增加的元素,a、b、c即为无偏hash函数,最下层则为二进制数组。

Redis面试题(2022)_第7张图片

3.2 空间计算

在布隆过滤器增加元素之前,首先需要初始化布隆过滤器的空间,也就是上面说的二进制数组,除此之外还需要计算无偏hash函数的个数。布隆过滤器提供了两个参数,分别是预计加入元素的大小n,运行的错误率f。布隆过滤器中有算法根据这两个参数会计算出二进制数组的大小l,以及无偏hash函数的个数k。 它们之间的关系比较简单:

错误率越低,位数组越长,控件占用较大 错误率越低,无偏hash函数越多,计算耗时较长

你可能感兴趣的:(redis,数据库,缓存)