Redis 常见面试题

  |   0 评论   |   0 浏览

Redis面试题

题号题目
1Redis的数据结构及使用场景
2Redis持久化的几种方式
3Redis的LRU具体实现
4单线程的Redis为什么快
5Redis的数据过期策略
6如何解决Redis缓存雪崩问题
7如何解决Redis缓存穿透问题
8Redis并发竞争key如何解决
9Redis的主从模式和哨兵模式和集群模式区别
10Redis事物的了解CheckAndSet操作实现乐观锁
11Redis有序集合zset底层怎么实现的
12跳表的查询过程是怎么样的,查询和插入的时间复杂度

Redis的数据结构及使用场景

  • String字符串

字符串类型是 Redis 最基础的数据结构,首先键都是字符串类型,而且 其他几种数据结构都是在字符串类型基础上构建的,我们常使用的 set key value 命令就是字符串。常用在缓存、计数、共享Session、限速等。

  • Hash哈希

在Redis中,哈希类型是指键值本身又是一个键值对结构,哈希可以用来存放用户信息,比如实现购物车。

  • List列表(双向链表)

列表(list)类型是用来存储多个有序的字符串。可以做简单的消息队列的功能。

  • Set集合

集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一 样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。
利用 Set 的交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。

  • Sorted Set有序集合(跳表实现)

Sorted Set 多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。可以做排行榜应用,取 TOP N 操作。

Redis持久化的几种方式

Redis为了保证效率,数据缓存在了内存中,但是会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件中,以保证数据的持久化。

Redis的持久化策略有两种:

  1. RDB:快照形式是直接把内存中的数据保存到一个dump的文件中,定时保存,保存策略。

当Redis需要做持久化时,Redis会fork一个子进程,子进程将数据写到磁盘上一个临时RDB文件中。当子进程完成写临时文件后,将原来的RDB替换掉。

  1. AOF:把所有的对Redis的服务器进行修改的命令都存到一个文件里,命令的集合。

使用AOF做持久化,每一个写命令都通过write函数追加到appendonly.aof中。
aof的默认策略是每秒钟fsync一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。

缺点是对于相同的数据集来说,AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB。

Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚刚连接的时候,进行全量同步(RDB),全同步结束后,进行增量同步(AOF)。

Redis的LRU具体实现

传统的LRU是使用栈的形式,每次都将最新使用的移入栈顶,但是用栈的形式会导致执行select *的时候大量非热点数据占领头部数据,所以需要改进。
Redis每次按key获取一个值的时候,都会更新value中的lru字段为当前秒级别的时间戳。Redis初始的实现算法很简单,随机从dict中取出五个key,淘汰一个lru字段值最小的。
在3.0的时候,又改进了一版算法,首先第一次随机选取的key都会放入一个pool中(pool的大小为16),pool中的key是按lru大小顺序排列的。
接下来每次随机选取的keylru值必须小于pool中最小的lru才会继续放入,直到将pool放满。放满之后,每次如果有新的key需要放入,需要将pool中lru最大的一个key取出。淘汰的时候,直接从pool中选取一个lru最小的值然后将其淘汰。

单线程的Redis为什么快

  • 纯内存操作
  • 单线程操作,避免了频繁的上下文切换
  • 合理高效的数据结构
  • 采用了非阻塞I/O多路复用机制

Redis的数据过期策略

Redis 中数据过期策略采用定期删除和惰性删除策略:

  • 定期删除策略

Redis 启用一个定时器定时监视所有的 key,判断key是否过期,过期的话就删除。

这种策略可以保证过期的 key 最终都会被删除,但是也存在严重的缺点:每次都遍历内存中所有的数据,非常消耗 CPU资源,并且当 key 已过期,但是定时器还处于未唤起状态,这段时间内 key 仍然可以用。

  • 惰性删除策略

在获取 key 时,先判断 key 是否过期,如果过期则删除。

这种方式存在一个缺点:如果这个 key一直未被使用,那么它一直在内存中,其实它已经过期了,会浪费大量的空间。

这两种策略天然的互补,结合起来之后,定时删除策略就发生了一些改变,不在是每次扫描全部的 key 了,而是随机抽取一部分 key 进行检查,这样就降低了对 CPU 资源的损耗,惰性删除策略互补了为检查到的key,基本上满足了所有要求。

但是有时候就是那么的巧,既没有被定时器抽取到,又没有被使用,这些数据又如何从内存中消失?

这个时候就需要用到了,内存淘汰机制.

内存淘汰机制分为:

  • 当内存不足以容纳新写入数据时,新写入操作会报错。(Redis 默认策略)
  • 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。(LRU推荐使用)
  • 当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。
  • 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。
  • 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。
  • 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。

如何解决Redis缓存雪崩问题

  • 使用 Redis 高可用架构:使用 Redis 集群来保证 Redis 服务不会挂掉
  • 缓存时间不一致,给缓存的失效时间,加上一个随机值,避免集体失效
  • 限流降级策略:有一定的备案,比如个性推荐服务不可用了,换成热点数据推荐服务

如何解决Redis缓存穿透问题

  • 在接口层做校验
  • 存null值(缓存击穿加锁)
  • 布隆过滤器拦截:将所有可能的查询key 先映射到布隆过滤器中,查询时先判断key是否存在布隆过滤器中,存在才继续向下执行,如果不存在,则直接返回。
    布隆过滤器将值进行多次哈希bit存储,布隆过滤器说某个元素在,可能会被误判。布隆过滤器说某个元素不在,那么一定不在。

Redis并发竞争key如何解决

  • 可以利用分布式锁和时间戳来解决.
  • 利用消息队列解决.

Redis的主从模式和哨兵模式和集群模式区别

Redis集群方式共有三种:主从模式,哨兵模式,集群(cluster)模式

  • 主从模式

主从模式是三种集群方式里最简单的。它主要是基于Redis的主从复制特性架构的。通常我们会设置一个主节点,N个从节点;默认情况下,主节点负责处理使用者的IO操作,而从节点则会对主节点的数据进行备份,并且也会对外提供读操作的处理。

主要的特点如下:

  1. 主从模式下,当某一节点损坏时,因为其会将数据备份到其它Redis实例上,这样做在很大程度上可以恢复丢失的数据。
  2. 主从模式下,可以保证负载均衡.
  3. 主从模式下,主节点和从节点是读写分离的。使用者不仅可以从主节点上读取数据,还可以很方便的从从节点上读取到数据,这在一定程度上缓解了主机的压力。

从节点也是能够支持写入数据的,只不过从从节点写入的数据不会同步到主节点以及其它的从节点下。

从以上,我们不难看出Redis在主从模式下,必须保证主节点不会宕机——一旦主节点宕机,其它节点不会竞争称为主节点,此时,Redis将丧失写的能力。这点在生产环境中,是致命的。

  • 哨兵模式

哨兵模式是基于主从模式做的一定变化,它能够为Redis提供了高可用性。

在实际生产中,服务器难免不会遇到一些突发状况:服务器宕机,停电,硬件损坏等。这些情况一旦发生,其后果往往是不可估量的。

而哨兵模式在一定程度上能够帮我们规避掉这些意外导致的灾难性后果。其实,哨兵模式的核心还是主从复制。

只不过相对于主从模式在主节点宕机导致不可写的情况下,多了一个竞选机制——从所有的从节点竞选出新的主节点。竞选机制的实现,是依赖于在系统中启动一个sentinel进程。

sentinel特点:

  • 监控:它会监听主服务器和从服务器之间是否在正常工作。
  • 通知:它能够通过API告诉系统管理员或者程序,集群中某个实例出了问题。
  • 故障转移:它在主节点出了问题的情况下,会在所有的从节点中竞选出一个节点,并将其作为新的主节点。
  • 提供主服务器地址:它还能够向使用者提供当前主节点的地址。这在故障转移后,使用者不用做任何修改就可以知道当前主节点地址。

sentinel,也可以集群,部署多个哨兵,sentinel可以通过发布与订阅来自动发现Redis集群上的其它sentinel。sentinel在发现其它sentinel进程后,会将其放入一个列表中,这个列表存储了所有已被发现的sentinel。

集群中的所有sentinel不会并发着去对同一个主节点进行故障转移。故障转移只会从第一个sentinel开始,当第一个故障转移失败后,才会尝试下一个。

当选择一个从节点作为新的主节点后,故障转移即成功了(而不会等到所有的从节点配置了新的主节点后)。这过程中,如果重启了旧的主节点,那么就会出现无主节点的情况,这种情况下,只能重启集群。

当竞选出新的主节点后,被选为新的主节点的从节点的配置信息会被sentinel改写为旧的主节点的配置信息。完成改写后,再将新主节点的配置广播给所有的从节点。

  • 集群模式

Redis 集群是一个提供在多个Redis间节点间共享数据的程序集, 其中Redis集群分为主节点和从节点。主节点用于处理槽,而从节点用于复制某个主节点,并在被复制的主节点下线时,代替下线的主节点继续处理命令请求。

Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.

Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令. Redis 集群的优势:

自动分割数据到不同的节点上。

整个集群的部分节点失败或者不可达的情况下能够继续处理命令。

Redis集群的数据分片 Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念.

Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽.

例如,当前集群有3个节点,那么:

  • 节点 A 包含 0 到 5500号哈希槽.
  • 节点 B 包含5501 到 11000 号哈希槽.
  • 节点 C 包含11001 到 16384号哈希槽.

这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上.

如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可.

由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.

Redis 集群的主从复制模型 为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.

Redis事物的了解CheckAndSet操作实现乐观锁

和众多其它数据库一样,Redis作为NoSQL数据库也同样提供了事务机制。在Redis中,MULTI,EXEC,DISCARD,WATCH这四个命令是我们实现事务的基石。

相信对有关系型数据库开发经验的开发者而言这一概念并不陌生,即便如此,我们还是会简要的列出 Redis中事务的实现特征 :

1.在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行。

2.和关系型数据库中的事务相比,在Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。

3.我们可以通过MULTI命令开启一个事务,有关系型数据库开发经验的人可以将其理解为"BEGIN TRANSACTION"语句。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC,DISCARD命令来提交,回滚该事务内的所有操作。

这两个Redis命令可被视为等同于关系型数据库中的COMMIT/ROLLBACK语句。

4.在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行。

5.当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。

Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。

此时,我们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。

Redis有序集合zset底层怎么实现的

Redis中的set数据结构底层用的是跳表实现的.

跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。

跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。

跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。

(1)跳表是可以实现二分查找的有序链表;
(2)每个元素插入时随机生成它的level;
(3)最低层包含所有的元素;
(4)如果一个元素出现在level(x),那么它肯定出现在x以下的level中;
(5)每个索引节点包含两个指针,一个向下,一个向右;
(6)跳表查询、插入、删除的时间复杂度为O(log n),与平衡二叉树接近;

为什么Redis选择使用跳表而不是红黑树来实现有序集合?(O(logN))

首先,我们来分析下Redis的有序集合支持的操作:

  • 插入元素
  • 删除元素
  • 查找元素
  • 有序输出所有元素
  • 查找区间内所有元素

其中,前4项红黑树都可以完成,且时间复杂度与跳表一致。但是,最后一项,红黑树的效率就没有跳表高了。
在跳表中,要查找区间的元素,我们只要定位到两个区间端点在最低层级的位置,然后按顺序遍历元素就可以了,非常高效。

而红黑树只能定位到端点后,再从首位置开始每次都要查找后继节点,相对来说是比较耗时的。
此外,跳表实现起来很容易且易读,红黑树实现起来相对困难,所以Redis选择使用跳表来实现有序集合。

跳表的查询过程是怎么样的,查询和插入的时间复杂度

先从第一层查找,不满足就下沉到第二层找,因为每一层都是有序的,写入和插入的时间复杂度都是O(logN)


标题:Redis 常见面试题
作者:疲惫的怪神明