Redis高频面试题

什么是 redis?有哪些应用场景?

  • redis是什么:Redis 即 Remote Dictionary Server,用中文翻译过来可以理解为远程数据服务或远程字典服务。其是使用 C 语言的编写的key-value键值对存储系统。
  • 应用场景:缓存、非关系型数据库、消息队列、分布式锁、点赞列表,排行榜等

redis 有哪八种数据类型?有哪些应用场景?

        Redis 总共有八种数据结构,五种基本数据类型和三种特殊数据类型。

五种基本数据类型:

  • string:字符串类型,常被用来存储计数器,粉丝数等,简单的分布式锁也会用到该类型。
  • hash:key-value 形式的,value 是一个map。
  • list:基本的数据类型,列表。在 Redis 中可以把 list 用作栈、队列、阻塞队列。
  • set:集合,不能有重复元素,可以做点赞,收藏等。
  • zset:有序集合,不能有重复元素,有序集合中的每个元素都需要指定一个分数,根据分数对元素进行升序排序。可以做排行榜

三种特殊数据类型:

  • geospatial:Redis 在 3.2 推出 Geo 类型,该功能可以推算出地理位置信息,两地之间的距离。
  • hyperloglog:基数:数学上集合的元素个数,是不能重复的。这个数据结构常用于统计网站的 UV。
  • bitmap:bitmap 就是通过最小的单位 bit 来进行0或者1的设置,表示某个元素对应的值或者状态。一个 bit 的值,或者是0,或者是1;也就是说一个 bit 能存储的最多信息是2。bitmap 常用于统计用户信息比如活跃粉丝和不活跃粉丝、登录和未登录、是否打卡等。

redis为什么这么快?

        Redis的性能非常之高,每秒可以承受10W+的QPS,它如此优秀的性能主要取决于以下几个方面:

纯内存操作、使用IO多路复用技术、非CPU密集型任务、单线程的优势

纯内存操作

        Redis是一个内存数据库,它的数据都存储在内存中,这意味着读写数据都是在内存中完成,这个速度是非常快的。

        Redis是一个KV内存数据库,它内部构建了一个哈希表,根据指定的KEY访问时,只需要O(1)的时间复杂度就可以找到对应的数据。同时,Redis提供了丰富的数据类型,并使用高效的操作方式进行操作,这些操作都在内存中进行,并不会大量消耗CPU资源,所以速度极快。

使用IO多路复用技术

        Redis采用单线程,那么它是如何处理多个客户端连接请求呢?

        Redis采用了IO多路复用技术和非阻塞IO,这个技术由操作系统实现提供,Redis可以方便地操作系统的API即可。Redis可以在单线程中监听多个Socket的请求,在任意一个Socket可读/可写时,Redis去读取客户端请求,在内存中操作对应的数据,然后再写回到Socket中。

        整个过程非常高效,Redis利用了IO多路复用技术的事件驱动模型,保证在监听多个Socket连接的情况下,只针对有活动的Socket采取反应。

非CPU密集型任务

        采用单线程的缺点很明显,无法使用多核CPU。

        Redis作者提到,由于Redis的大部分操作并不是CPU密集型任务,而Redis的瓶颈在于内存和网络带宽。 在高并发请求下,Redis需要更多的内存和更高的网络带宽,否则瓶颈很容易出现在内存不够用和网络延迟等待的情况。

        当然,如果觉得单个Redis实例的性能不足以支撑业务,Redis作者推荐部署多个Redis节点,组成集群的方式来利用多核CPU的能力,而不是在单个实例上使用多线程来处理。

单线程的优势基于以上特性

        Redis采用单线程已足够达到非常高的性能,所以Redis没有采用多线程模型。另外,单线程模型还带了以下好处:
○没有了多线程上下文切换的性能损耗
○没有了访问共享资源加锁的性能损耗
○开发和调试非常友好,可维护性高
所以Redis正是基于以上这些方面,所以采用了单线程模型来完成请求处理的工作。

多线程优化

        Redis Server本身是多线程的,除了请求处理流程是单线程处理之外,Redis内部还有其他工作线程在后台执行,它负责异步执行某些比较耗时的任务,例如AOF每秒刷盘、AOF文件重写都是在另一个线程中完成的。

而在Redis 4.0之后,Redis引入了lazyfree的机制,提供了unlink、flushall aysc、flushdb async等命令和lazyfree-lazy-eviction、lazyfree-lazy-expire等机制来异步释放内存,它主要是为了解决在释放大内存数据导致整个redis阻塞的性能问题。

        在删除大key时,释放内存往往都比较耗时,所以Redis提供异步释放内存的方式,让这些耗时的操作放到另一个线程中异步去处理,从而不影响主线程的执行,提高性能。

        到了Redis 6.0,Redis又引入了多线程来完成请求数据的协议解析,进一步提升性能。它主要是解决高并发场景下,单线程解析请求数据协议带来的压力。请求数据的协议解析由多线程完成之后,后面的请求处理阶段依旧还是单线程排队处理。

        可见,Redis并不是保守地认为单线程有多好,也不是为了使用多线程而引入多线程。Redis作者很清楚单线程和多线程的使用场景,针对性地优化,这是非常值得学习的。

缺点:

上面介绍了单线程可以达到如此高的性能,并不是说它就没有缺点了。
        单线程处理最大的缺点就是,如果前一个请求发生耗时比较久的操作,那么整个Redis就会阻塞住,其他请求也无法进来,直到这个耗时久的操作处理完成并返回,其他请求才能被处理到。
        平时遇到Redis变慢或长时间阻塞的问题,90%也都是因为Redis处理请求是单线程这个原因导致的。
        所以在使用Redis时,一定要避免非常耗时的操作,例如使用时间复杂度过高的方式获取数据、一次性获取过多的数据、大量key集中过期导致Redis淘汰key压力变大等等,这些场景都会阻塞住整个处理线程,直到它们处理完成,势必会影响业务的访问。

总结:

        Redis使用单线程,配合IO多路复用技术,可以完成多个连接的请求处理。而且正是由于它的使用定位是内存数据库,这样几乎所有的操作都在内存中完成,它的性能可以达到非常之高。
        同时,单线程没有了线程上下文切换和访问共享资源加锁的性能损耗,而且单线程模型对程序的开发和调试非常友好,因此Redis使用单线程模型也就在情理之中了。
        Redis在最近的版本也对多线程进行了优化,用于解决释放大内存数据和请求数据协议解析对Redis产生的性能影响,进一步提升了Redis的性能。
        单线程结合上述场景可以达到非常高的性能,同时也存在耗时操作阻塞整个线程的问题,在使用Redis时要避免耗时过长的操作,才能更好地发挥Redis的性能。

听说 redis 6.0之后又使用了多线程,不会有线程安全的问题吗?

不会

        Redis 还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程,所以是不会有线程安全的问题。
        之所以加入了多线程因为 Redis 的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高Redis的性能。

redis 的持久化机制有哪些?区别是什么?

redis 有两种持久化的方式,AOF 和 RDB。

  •  RDB【快照思想】
    • 把某个时间点 redis 内存中的数据以二进制的形式存储的一个dump.rdb为后缀的文件当中,也就是【周期性的备份redis中的整个数据】,这是redis默认的持久化方式,也就是说的快照(snapshot),是采用 fork 子进程的方式来写时同步的。
save 3600 1     #表示1小时内至少1个键被更改则进行快照。
save 300 100    #表示5分钟(300秒)内至少100个键被更改则进行快照。
save 60 10000   #表示1分钟内至少10000个键被更改则进行快照。
  • 优点:
    • 它是将某一时间点redis内的所有数据保存下来,所以当做大型的数据恢复时,RDB的恢复速度会很快
    • 由于RDB的FROK子进程这种机制,队友给客户端提供读写服务的影响会非常小
    • 基于二进制文件完成数据备份,占用空间少,便于文件传输。
    • 能够自定义规则,根据Redis繁忙状态进行数据备份。
  • 缺点:
    • 无法保证数据完整性,会丢失最后一次快照后的所有数据。
    • bgsave执行每次执行都会阻塞Redis服务进程创建子线程,频繁执行影响系统吞吐率。
  • AOF(append only file)文件追加

当开启AOF持久化后,Redis会将客户端发送的所有更改数据的命令,记录到磁盘中的AOF文件。 这样的话,当Redis重启后,通过读取AOF文件,按顺序获取到记录的数据修改命令,即可完成数据恢复。 默认不开启,开启后存在的配置如下:

模式

aof_buf写入到AOF是否阻塞

AOF文件写入磁盘是否阻塞

宕机重启时丢失的数据量

效率

安全

always

阻塞

阻塞

最多只丢失一个命令的数据

everysec

阻塞

不阻塞

不超过两秒的数据

no

阻塞

阻塞

操作系统最后一次对AOF写入磁盘的数据

  • 优点:
    • AOF会以每隔1秒,通过后台的一个线程去执行一次fsync操作,如果redis进程挂掉,最多丢失1秒的数据
    • AOF是将命令直接追加在文件末尾的,写入性能较高
  • 缺点:
    • AOF 文件比 RDB 数据文件快照要大
    • 由于 .aof 的每次命令都会写入,那么相对于 RDB 来说「需要消耗的性能也就更多」,当然也会有 aof 重写将 aof 文件优化
    • 数据恢复比较慢
  • RDB与AOF对比
    • RDB默认开启,AOF需手动开启。
    • RDB性能优于AOF。
    • AOF安全性优于RDB。
    • AOF优先级高于RDB(混合模式)。
    • RDB存储某个时刻的数据快照,AOF存储写命令。
    • RDB在配置触发状态会丢失最后一次快照以后更改的所有数据,AOF默认使用everysec,每秒保存一次,最多丢失两秒以内的数据。

Redis的过期键的删除策略有哪些?

场景面试题:如果一个key过期了,那么它实际是在什么时候被删除的呢?
可能很多同学的答案是: 到期了就直接被删除了。 但是这里告诉大家,这个答案是错误的。
这就需要给大家介绍下,Redis中对于过期键的过期删除策略:

  • 定时删除
    • ​​​​​​​它会在设置键的过期时间的同时,创建一个定时器, 当键到了过期时间,定时器会立即对键进行删除。 这个策略能够保证过期键的尽快删除,快速释放内存空间。
    • Redis的操作频率是非常高的。绝大多数的键都是携带过期时间的,这样就会造成出现大量定时器执行,严重降低系统性能。
    • 该策略对内存空间足够友好, 但对CPU非常不友好,会拉低系统性能,因此不建议使用。
  • 惰性删除
    • ​​​​​​​​​​​​​​它不持续关注key的过期时间, 而是在获取key时,才会检查key是否过期,如果过期则删除该key。
    • 虽然它解决了定时删除会占用大量CPU资源的问题, 但是它又会造成内存空间的浪费。假设Redis中现在存在大量过期key,而这些过期key如果都不被使用,它们就会保留在redis中,造成内存空间一直被占用。
    • 惰性删除对CPU足够友好,但是对内存空间非常不友好,会造成大量内存空间的浪费。
  • ​​​​​​​定期删除
    • ​​​​​​​​​​​​​​默认每秒运行10次会对具有过期时间的key进行一次扫描,但是并不会扫描全部的key,因为这样会大大延长扫描时间。
    • 每次默认只会随机扫描20个key,同时删除这20个key中,已经过期的key。
    • 如果这20个key中过期key的比例达超过25%,则继续扫描。

Redis 有哪些部署方式?

  • 单机模式:这也是最基本的部署方式,只需要一台机器,负责读写,一般只用于开发人员自己测试
  • 哨兵模式:哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。它具备自动故障转移、集群监控、消息通知等功能。
  • Cluster集群模式:在redis3.0版本中支持了cluster数据分片集群部署的方式,这种集群部署的方式能自动将数据进行分片,每个master上放一部分数据,提供了内置的高可用服务,即使某个master挂了,服务还可以正常地提供。
  • Master-Slave主从复制:在主从复制这种集群部署模式中,会将数据库分为两类,第一种称为主数据库(master),另一种称为从数据库(slave)。主写从读。其中在企业开发中的真实情况是,会让主数据库只负责写操作,让从数据库只负责读操作,就是为了读写分离,减轻服务器的压力。

Memcache与Redis的区别都有哪些?

维度

Redis

Memecache

存储方式

内存+磁盘

内存

支持数据类型

8种

String

存储值限制

512M

1M

执行速度

10W/s

1W/s

数据持久化

支持

不支持

网络IO模型

单进程模式

多线程、非阻塞IO模式

Redis基本数据类型的底层数据结构有哪些?

字符串:redis没有直接使用C语言传统的字符串表示,而是自己实现的叫做简单动态字符串SDS的抽象类型。C语言的字符串不记录自身的长度信息,而SDS则保存了长度信息,这样将获取字符串长度的时间由O(N)降低到了O(1),同时可以避免缓冲区溢出和减少修改字符串长度时所需的内存重分配次数。


链表linkedlist:redis链表是一个双向无环链表结构,很多发布订阅、慢查询、监视器功能都是使用到了链表来实现,每个链表的节点由一个listNode结构来表示,每个节点都有指向前置节点和后置节点的指针,同时表头节点的前置和后置节点都指向NULL。


字典hashtable:用于保存键值对的抽象数据结构。redis使用hash表作为底层实现,每个字典带有两个hash表,供平时使用和rehash时使用,hash表使用链地址法来解决键冲突,被分配到同一个索引位置的多个键值对会形成一个单向链表,在对hash表进行扩容或者缩容的时候,为了服务的可用性,rehash的过程不是一次性完成的,而是渐进式的。


跳跃表skiplist:跳跃表是有序集合的底层实现之一,redis中在实现有序集合键和集群节点的内部结构中都是用到了跳跃表。redis跳跃表由zskiplist和zskiplistNode组成,zskiplist用于保存跳跃表信息(表头、表尾节点、长度等),zskiplistNode用于表示表跳跃节点,每个跳跃表的层高都是1-32的随机数,在同一个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是唯一的,节点按照分值大小排序,如果分值相同,则按照成员对象的大小排序。


整数集合intset:用于保存整数值的集合抽象数据结构,不会出现重复元素,底层实现为数组。


压缩列表ziplist:压缩列表是为节约内存而开发的顺序性数据结构,他可以包含多个节点,每个节点可以保存一个字节数组或者整数值。


基于这些基础的数据结构,redis封装了自己的对象系统,包含字符串对象string、列表对象list、哈希对象hash、集合对象set、有序集合对象zset,每种对象都用到了至少一种基础的数据结构。
redis通过encoding属性设置对象的编码形式来提升灵活性和效率,基于不同的场景redis会自动做出优化。不同对象的编码如下:

  • 字符串对象string:int整数、embstr编码的简单动态字符串、raw简单动态字符串
  • 列表对象list:ziplist、linkedlist
  • 哈希对象hash:ziplist、hashtable
  • 集合对象set:intset、hashtable\
  • 有序集合对象zset:ziplist、skiplist
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦子然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值