6.redis面试题

62 阅读21分钟

1. redis的数据类型有哪些?适用场景是什么

  1. String:
  • 适用于单值缓存

  • 分布式锁

  • 计数器

  1. Hash:
  • 对象存储

  1. list

  • Stack(栈FILO) = LPUSH + LPOP
    Queue(队列FIFO)= LPUSH + RPOP Blocking MQ(阻塞队列)= LPUSH + BRPOP

  • 消息队列:

    list类型的lpush和rpush(或者反过来rpush和lpop),能实现队列的功能(先进先出),故而可以使用redis的list类型实现简单的点对点的消息队列的功能

  • 分页查询:

    list类型的lrange命令可以分页查看队列中的数据

  • 最新列表:

    list类型的lpush和lrange命令能实现获取最新列表的功能,每次它通过lpush命令往list中插入新的元素,然后通过lrange命令读取最新的元素列表,如朋友圈的点赞列表、评论列表。

  1. Set:
  • 利用值的唯一性,可以统计访问网站的所有独立 IP

  • 利用值的唯一性,对两个集合间的数据进行交集、并集、差集运算的操作,来推荐好友和获取共同好友等

  • 电商网站商品筛选:利用交集操作可以筛选出多条件下符合条件的商品。

  1. SortSet:

热搜排行榜

2. 一个字符串类型的值能存储最大容量是多少?

512M

3. redis是单线程还是多线程?为什么效率这么高

Redis 确实是单线程的。通常说的单线程,主要是指 Redis 对外提供的键值存储服务的主要流程是单线程,也就是网络IO和数据读写是由单个线程来完成的。除此外 Redis 的其他功能,比如持久化、 异步删除、集群数据同步等,是由额外的线程来执行的。这样做的好处是可以 。因此,严格地说 Redis 并不是全面单线程。

redis5及之前版本(网络IO和数据读写都是单线程)

Redis5及之前的版本使用的是 单线程,也就是说只有一个 worker队列,所有的读写操作都要在这一个队列进行操作,好处是不会有线程安全问题(因为它在读写时就只有一个线程,那对于读写操作肯定没有线程安全问题啊!),但是读写 write、read 这些系统调用在Redis执行期间占用了大部分的 CPU 时间,所以这就是单线程模式的缺点。 所以也就在Redis6引入了多线程版本

redis6(网络IO多线程,数据读写单线程)

Redis6引入了多线程机制,但是不是说有多个worker线程同时并发读写, 而是它有 “一个 worker线程+多个IO子线程”,通过多个IO线程并行处理网络操作,其实就是在 IO 就绪之后使用多线程提升读写解析数据的效率,而在 操作内存数据的时候还是用单线程。利用这种单线程+多线程共同运作的机制,将CPU的性能显著提升了。

4. Redis为什么这么快?

  1. 完全基于内存存储实现

完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

  1. 单线程模型

Redis是单线程模型的,而单线程避免了CPU不必要的上下文切换和竞争锁的消耗。也正因为是单线程,如果某个命令执行过长(如keys,hgetall命令),会造成排队阻塞。

Redis 6.0 引入了多线程提速,它的执行命令操作内存的仍然是个单线程的。

  1. 合理的线程模型

使用多路I/O复用模型,非阻塞IO;

多路I/O复用技术可以让单个线程高效的处理多个连接请求,而Redis使用epoll作为I/O多路复用技术的实现。并且,Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。

5. 为什么Redis 6.0 之后改多线程呢?

Redis6.0之前,Redis在处理客户端的请求时,包括读socket、解析、执行、写socket等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。

redis使用多线程并非是完全摒弃单线程,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程。对于数据的读写还是使用单线程模型来处理。

这样做的目的是因为redis的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高redis的性能。

6. 你了解Redis的过期策略吗?

我们在set key的时候,可以给它设置一个过期时间,比如expire key 60。指定这key 60s后过期,60s后redis是如何处理的?我们先来介绍几种过期策略:

  1. 定时过期

每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

  1. 惰性过期

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

  1. 定期过期

每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

Redis中同时使用了惰性过期和定期过期两种过期策略。

假设Redis当前存放30万个key,并且都设置了过期时间,如果你每隔100ms就去检查这全部的key,CPU负载会特别高,最后可能会挂掉。

因此,redis采取的是定期过期,每隔100ms就随机抽取一定数量的key来检查和删除的。

但是呢,最后可能会有很多已经过期的key没被删除。这时候,redis采用惰性删除。在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会删除。

但是呢,如果定期删除漏掉了很多过期的key,然后也没走惰性删除。就会有很多过期key积在内存中,直接会导致内存爆的。或者有些时候,业务量大起来了,redis的key被大量使用,内存直接不够了, 这个时候就需要内存淘汰策略来保护自己了。

7. 聊聊Redis内存淘汰策略?

  1. volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰;
  2. allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU(最近最少使用)算法进行淘汰;
  3. volatile-lfu:4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU算法进行删除key;
  4. allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰;
  5. volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据;
  6. allkeys-random:当内存不足以容纳新写入数据时,从所有key中随机淘汰数据;
  7. volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰;
  8. noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错;

8. 聊聊Redis事务机制?

Redis通过MULTI、EXEC、WATCH等一组命令集合,来实现事务机制。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

简言之,Redis事务就是顺序性、一次性、排他性的执行一个队列中的一系列命令。 有关redis事务需要注意的就是

1)与mysql中事务不同,在redis事务遇到执行错误的时候,不会进行回滚,而是简单的放过了,并保证其他的命令正常执行(所以说redis的事务并不是保证原子性)。

2)当事务的执行过程中,如果redis意外的挂了。很遗憾只有部分命令执行了,后面的也就被丢弃了。

9. 说说Redis哈希槽的概念?

Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有16384个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。

使用哈希槽的好处就在于可以方便的添加或移除节点。这种结构无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。

当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;

当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;

在这一点上,我们以后新增或移除节点的时候不用先停掉所有的 redis 服务。

10.为什么RedisCluster会设计成16384个槽呢?

2的14次方就是16384,这个当然不说一定要设计成16384个槽

  1. 如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。

在消息头中,当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb 因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。

  1. redis的集群主节点数量基本不可能超过1000个。

集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。

  1. 槽位越小,节点少的情况下,压缩率高

Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。

如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。

11. Redis在集群中查找key的时候,是怎么定位到具体节点的?

使用CRC16算法对key进行hash,再将hash值对16384取模,得到具体的槽位根据节点和槽位的映射信息(与集群建立连接后,客户端可以取得槽位映射信息),找到具体的节点地址 去具体的节点找key如果key不在这个节点上,则redis集群会返回moved指令,加上新的节点地址给客户端。

同时,客户端会刷新本地的节点槽位映射关系如果槽位正在迁移中,那么redis集群会返回asking指令给客户端,这是临时纠正,客户端不会刷新本地的节点槽位映射关系。

12. Redis的Hash冲突怎么办

Redis 中的 Hash和 Java的 HashMap 更加相似,都是数组+链表的结构,当发生 hash 发生碰撞时将会把元素追加到链表上。

在Redis中hash的内部结构也是一样的: 第一维是数组,第二维是链表.组成一个 全局哈希表。

在 Java 中 HashMap 扩容是个很耗时的操作,需要去申请新的数组,扩容的成本并不低,因为需要遍历一个时间复杂度为O(n)的数组,并且为其中的每个enrty进行hash计算。加入到新数组中。

为了追求高性能,Redis 采用了渐进式 rehash 策略.这也是 hash 中最重要的部分.

redis在扩容的时候执行 rehash 策略会保留新旧两个全局哈希表,查询时也会同时查询两个全局哈希表 ,Redis会将旧 全局哈希表 中的内容一点一点的迁移到新的 全局哈希表 中,当迁移完成时,就会用新的 全局哈希表 取代之前的。当 全局哈希表 移除了最后一个元素之后,这个数据结构将会被删除.

正常情况下,当 全局哈希表 中元素的个数等于数组的长度时,就会开始扩容,扩容的新数组是原数组大小的 2 倍。

如果 Redis 正在做 bgsave(持久化) 时,可能不会去扩容,因为要减少内存页的过多分离(Copy On Write).但是如果 全局哈希表 已经非常满了,元素的个数达到了数组长度的 5 倍时,Redis 会强制扩容。

13. 有哪些办法可以降低 Redis 的内存使用情况呢?

  1. 控制 key 的长度
  • 保证 key 在简单、清晰的前提下,尽可能把 key 定义得短一些

  1. 避免存储 bigkey:
  • bigkey,意思就是这个key的value值很大。除了控制 key 的长度之外,你同样需要关注 value 的大小,如果大量存储 bigkey,也会导致 Redis 内存增长过快。

  1. 尽可能地都设置过期时间

  2. 实例设置 maxmemory + 淘汰策略

14. 说说redis的持久化机制?

redis有两种持久化机制

  1. RDB持久化

  • 定义:在指定时间间隔内,将内存中的数据集快照写入磁盘中,其实际工作过程是,先创建一个子进程,将数据集写入一个临时文件,写入成功后,再替换掉之前的文件,用二进制压缩存储。

  • 优点:

  1. 方便持久化和备份

  2. RDB创建子进程完成写操作,而主进程继续处理命令,IO最大化、保证redis的高性能

  3. 当数据集较大时,RDB的骑当效率比AOF更高

  • 缺点:

  1. 数据安全性低,因为RDB是隔一段时间才进行持久化,如果在这个间隔期间发生了故障,那么这期间的数据将会丢失。

  2. 当数据集较大时,可能会导致服务器停止服务几百毫秒

  3. AOF持久化机制

  • 定义:用日志的形式记录服务器所处理的每一个写、删除操作,查询操作不记录,用文本的方式进行记录,可以打开文件看到详细的操作记录。在重新启动redis时,redis会根据日志文件的内容将指令从前到后执行一次。

  • 优点:

  1. AOF有三种同步策略,数据安全性比RDB高。

  2. AOF通过append模式写文件,即使中途服务器宕机也不会破坏已存在的内容

  3. AOF有rewrite模式,定期对AOF文件进行重写,来达到压缩的目的

  • 缺点:
  1. AOF文件比RDB文件大,且恢复速度慢,运行效率比RDB低,不适用于数据集大的时候,若AOF和RDB同时开启,系统会默认取AOF中的数据。

15. redis持久化机制该如何选择?

  1. 如果数据不能丢失,RDB和AOF混用
  2. 如果只作为缓存使用,可以承受几分钟的数据丢失的话,可以只使用RDB。
  3. 如果只使用AOF,优先使用everysec的写回策略。
  4. 如果只是做纯内存缓存,可以都不用。

16. redis集群有哪几种方式?

  1. 主从复制:

  • 模式: 主从复制模式包含一个主数据库,一个或多个从数据库。客户端可以对主数据库进行读写操作,对从数据库只能进行读操作,主数据库写入的数据会实时自动同步给从数据库。

  • 具体步骤: ①slave启动后,向master发送同步命令,master接收到同步命令后通过bgsave命令保存快照,并使用缓冲区记录保存快照这段时间内执行的写命令。 ②master将保存的快照文件发送给slave,并继续记录执行的写命令。 ③slave接收到快照文件后,加载快照文件,载入数据。 ④master快照发送完后开始向slave发送缓冲区的写命令,slave接收命令并执行,完成复制初始化。 ⑤此后master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致性

  • 优点: ①master能自动将数据同步到slave,并且读写分离,可以分担master的读的压力。 ②master和slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以处理客户端请求

  • 缺点: ①不具备自动容错与恢复功能,master或slave的宕机都可能导致客户端请求失败,需要等待机器重启或手动切换客户端IP才能恢复。 ②master宕机,如果宕机前数据没有同步完,则切换IP后会存在数据不一致的问题。


  1. 哨兵模式:

  • 模式: 哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障,当一个 master 宕机后,会从从服务器中选一个作为主机。如果只有一个哨兵进程对Redis服务器进行监控,当这个哨兵宕机后,就没办法对主服务器进行监控了,因此,我们一般使用多个哨兵进行监控。各个哨兵之间还会相互监控,这样就形成了多哨兵模式。

  • 优点: ①哨兵集群,基于主从复制模式,所有的主从配置优点,它全有。 ②主从可以切换,故障可以转移,系统的可用性就会更好。 ③哨兵模式就是主从模式的升级,手动到自动,更加健壮。

  • 缺点: ①难以支持在线扩容,Redis的容量受限于单机配置。 ②实现哨兵模式的配置很复杂,并且需要额外的资源来启动哨兵进程。


  1. Cluster模式:

  • 模式:

    1. Cluster模式实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题

    2. 所有的redis节点相互连接,内部使用二进制协议优化传输速度和带宽。

    3. 客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。(当Redis节点收到自己不负责的Slot的请求时,会将负责请求Key所在Slot的Redis节点地址返回给客户端,客户端收到后自动将原请求重新发往这个地址)

  • 优点:

    1. 可线性扩展到1000多个节点,节点可动态添加或删除。

    2. 能够实现自动故障转移。

  • 缺点:

    1. 数据通过异步复制,不保证数据的强一致性。
    2. slave只是备用,不能缓解“读操作”的压力。
    3. 不支持多数据库空间,单机redis可以支持16个db,集群模式下只能使用一个,即db0
    4. 对于像批量操作、事务操作等的支持性不够好。

17. 缓存穿透、缓存雪崩、缓存击穿该怎么解决

  1. 缓存穿透

(1)问题描述

key对应的数据在数据库中根本不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。

(2)解决方法:

①过滤非法查询:在后台服务中过滤非法查询,直接不让他落到Redis服务上。比如id<=0就直接断言。 ②缓存空对象:如果他的查询数据是合法的,但是确实Redis和MySql中都没有,那么我们就在Redis中储存一个空对象,这样下次客户端继续查询的时候就能在Redis中返回了。但是,如果客户端一直发送这种恶意查询,就会导致Redis中有很多这种空对象,浪费很多空间 ③采用布隆过滤器:它实际上是一个很长的二进制向量 (位图) 和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难


  1. 缓存雪崩

(1)问题描述

Redis中的缓存数据是有过期时间的,当在同一时间大量的缓存同时失效时,由于大量请求无法在redis缓存中处理,就会发送到数据库,导致数据库压力激增,可能会导致数据库崩溃,从而导致整个系统崩溃。 解决缓存雪崩的关键:避免Redis的缓存在短时间内大量的过期

(2)解决方法:

①根据业务需要来合理的设置过期的时间 ②使用redis分布式锁:让一时间只有一个相同请求落到MySql上,反正都是查询同一个信息,之后的其他请求就可以去Redis中找了。


  1. 缓存击穿:

缓存击穿和缓存雪崩类似,也是因为Redis中key过期导致的。只不过缓存击穿是某一个热点的key过期导致的。当有一个热点数据突然过期时,就会导致突然有大量的请求直接落到数据库上,导致数据库崩溃。 解决缓存击穿的要点,不让热点key突然过期

解决方案: ①热数据,设置永远不过期。 ②加锁对请求进行同步。锁内逻辑:再次查询缓存,查不到转查数据库并且进行数据缓存,后面的请求就直接缓存,也避免再次查数据库。

18 .如何解决redis缓存一致性问题

延迟双删: img

该方案有个非常关键的地方是:第二次删除缓存,并非立马就删,而是要在一定的时间间隔之后。

sleep的时间要对业务读写缓存的时间做出评估,sleep时间大于读写缓存的时间即可。

那么,为什么一定要间隔一段时间之后,才能删除缓存呢?

请求d卡顿结束,把新值写入数据库后,请求c将数据库中的旧值,更新到缓存中。此时,如果请求d删除太快,在请求c将数据库中的旧值更新到缓存之前,就已经把缓存删除了,这次删除就没任何意义。必须要在请求c更新缓存之后,再删除缓存,才能把旧值及时删除了。