redis-08 搞定 redis 所有面试

一、基础总结篇

1、谈一下 Redis 的优缺点

 优点:

  1. 读写性能优异, Redis能读的速度是 110000 次/s,写的速度是 81000 次/s

  2. 支持数据持久化,支持 AOF 和 RDB 两种持久化方式

  3. 支持事务,Redis 的所有操作都是原子性的

  4. 数据结构丰富,除了支持 string 类型的 value 外,还支持 list、hash、set、zset 等数据结构

  5. 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离

 缺点:

  1. 容量受物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。

  2. 不具备自动容错和恢复,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的 IP 才能恢复。

  3. 系统可用性不是很高,主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP 后还会引入数据不一致的问题,降低了系统的可用性

  4. Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。

2、你们为什么使用 Redis

 当前线上应用现状

  在日常的 Web 应用对数据库的访问中,读操作的次数远超写操作,比例大概在 1:9 到 3:7,所以需要读的可能性是比写的可能大得多的。当我们使用 SQL 语句去数据库进行读写操作时,数据库就会 去磁盘把对应的数据索引取回来,这是一个相对较慢的过程。

 使用缓存(Redis)带来的优势

  如果我们把数据放在 Redis 中,也就是直接放在内存之中,让服务端直接去读取内存中的数据,那么这样 速度 明显就会快上不少,提升系统的性能 (高性能),并且特别是在高并发的场景下会 极大减小数据库的压力

3、使用缓存中常见问题

 什么是缓存雪崩、缓存穿透 和 缓存击穿,对应的解决方案是什么?

  参考我的另一篇博客:redis-12 缓存雪崩、穿透和击穿详解  

 实际中缓存和数据库双写一致性问题如何解决?

  参考我的另一篇博客:redis-14 缓存和数据库双写一致性问题详解  

4、Redis 早期版本为什么选择单线程

 官方解释

  因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是 机器内存的大小 或者 网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。

 自我总结

  1. 使用单线程模型能带来更好的 可维护性,方便开发和调试;

  2. 使用单线程模型也能 并发 的处理客户端的请求;(I/O 多路复用机制)

  3. Redis 服务中运行的绝大多数操作的 性能瓶颈都不是 CPU

5、Redis 既然是单线程为什么还这么快

  • 纯内存操作:读取不需要进行磁盘 I/O,所以比传统数据库要快上不少;(但不要有误区说磁盘就一定慢,例如 Kafka 就是使用磁盘顺序读取但仍然较快)

  • 单线程,无锁竞争:这保证了没有线程的上下文切换,不会因为多线程的一些操作而降低性能;

  • 多路 I/O 复用模型+消息回调,非阻塞 I/O:采用多路 I/O 复用技术可以让单个线程高效的处理多个网络连接请求(尽量减少网络 IO 的时间消耗),例如使用了 epoll 且消息回调机制,充分的降低了用户态 和 内核态的耗时,详情可参考我的另外一篇博客:redis-10 redis和 I/O多路复用

  • 高效的数据结构,加上底层做了大量优化:Redis 对于底层的数据结构和内存占用做了大量的优化,例如不同长度的字符串使用不同的结构体表示等等..;详情可参考我的另外一篇博客:redis-02 五种类型底层数据结构

第二、数据结构篇

6、Redis 的 SDS 和 C 中字符串相比优势是什么

  C 语言使用了一个长度为 N+1 的字符数组来表示长度为 N 的字符串,并且字符数组最后一个元素总是 ,这种简单的字符串表示方式 不符合 Redis 对字符串在安全性、效率以及功能方面的要求。因为这种数据结构可能会造成以下一些问题:

  1. 获取字符串长度为 O(N) 级别的操作 → 因为 C 不保存数组的长度,每次都需要遍历一遍整个数组;

  2. 无法杜绝 缓冲区溢出/内存泄漏 的问题 → 跟上述问题原因一样,如果执行拼接 or 缩短字符串的操作,如果操作不当就很容易造成上述问题;

  3. C 字符串 只能保存文本数据 → 因为 C 语言中的字符串必须符合某种编码(比如 ASCII),例如中间出现的 '' 可能会被判定为提前结束的字符串而识别不了;

 Redis 是通过 SDS 来解决的

    

  如果去看 Redis 的源码 sds.h/sdshdr 文件,你会看到 SDS 完整的实现细节,这里简单来说一下 Redis 如何解决的:
    1. 多增加 len 表示当前字符串的长度:这样就可以直接获取长度了,复杂度 O(1);

    2. 自动扩展空间:当 SDS 需要对字符串进行修改时,首先借助于 lenalloc 检查空间是否满足修改所需的要求,如果空间不够的话,SDS 会自动扩展空间,避免了像 C 字符串操作中的覆盖情况;

    3. 有效降低内存分配次数:C 字符串在涉及增加或者清除操作时会改变底层数组的大小造成重新分配,SDS 使用了 空间预分配惰性空间释放 机制,简单理解就是每次在扩展时是成倍的多分配的,在缩容是也是先留着并不正式归还给 OS;

    4. 二进制安全:C 语言字符串只能保存 ascii 码,对于图片、音频等信息无法保存,SDS 是二进制安全的,写入什么读取就是什么,不做任何过滤和限制;

7、hash 是如何实现的?Rehash 有了解吗?

  Redis 中的字典相当于 Java 中的 HashMap,内部实现也差不多类似,都是通过 "数组 + 链表"链地址法 来解决部分 哈希冲突,同时这样的结构也吸收了两种不同数据结构的优点。

  字典结构内部包含 两个 hashtable,通常情况下只有一个 hashtable 有值,但是在字典扩容缩容时,需要分配新的 hashtable,然后进行 渐进式搬迁 (rehash),这时候两个 hashtable 分别存储旧的和新的 hashtable,待搬迁结束后,旧的将被删除,新的 hashtable 取而代之。

  

 Rehash即扩缩容

  正常情况下,当 hash 表中 元素的个数等于第一维数组的长度时就会开始扩容,扩容的新数组是 原数组大小的 2 倍不过如果 Redis 正在做 bgsave(持久化命令)为了减少内存过多的使用导致内存溢出的风险,Redis 尽量不去扩容,但是如果 hash 表非常满了,达到了第一维数组长度的 5 倍了这个时候就会 强制扩容

  当 hash 表因为元素逐渐被删除变得越来越稀疏时,Redis 会对 hash 表进行缩容来减少 hash 表的第一维数组空间占用。所用的条件是 元素个数低于数组长度的 10%缩容不会考虑 Redis 是否在做 bgsave

8、跳跃表底层是如何实现的?原理是什么

   请查看我的另一篇博客:redis-15 zset 底层跳表 skiplist 实现
 

 9、布隆过滤器有了解吗?底层原理怎么实现?

  请参考另一篇博客:亿级数据过滤和布隆过滤器
 

10、压缩列表了解吗?

  这是 Redis 为了节约内存 而使用的一种数据结构,zset 和 hash 容器对象会在元素个数较少的时候,采用压缩列表(ziplist)进行存储。压缩列表是 一块连续的内存空间,元素之间紧挨着存储,没有任何冗余空隙。具体请查看我的另一篇博客:redis-02 五种类型底层数据结构

11、快速列表 quicklist 了解吗?

   Redis 早期版本存储 list 列表数据结构使用的是压缩列表 ziplist 和普通的双向链表 linkedlist,也就是说当元素少时使用 ziplist,当元素多时用 linkedlist。但考虑到链表的附加空间相对较高,prevnext 指针就要占去 16 个字节(64 位操作系统占用 8 个字节),另外每个节点的内存都是单独分配,会家具内存的碎片化,影响内存管理效率。

  后来 Redis 新版本(3.2)对列表数据结构进行了改造,使用 quicklist 代替了 ziplist linkedlist

    具体请查看我的另一篇博客:redis-02 五种类型底层数据结构

第三、持久化篇

12、说一下 Redis 中的两种持久化方式

 方式一、快照 snapshot

  Redis 快照 是最简单的 Redis 持久化模式。当满足特定条件时,它将生成数据集的时间点快照,例如,如果先前的快照是在 2 分钟前创建的,并且现在已经至少有 100 次新写入,则将创建一个新的快照。此条件可以由用户配置 Redis 实例来控制,也可以在运行时修改而无需重新启动服务器。快照作为包含整个数据集的单个 .rdb 文件生成。

  方式二、AOF

  快照不是很持久。如果运行 Redis 的计算机停止运行,电源线出现故障或者您 kill -9 的实例意外发生,则写入 Redis 的最新数据将丢失。尽管这对于某些应用程序可能不是什么大问题,但有些使用案例具有充分的耐用性,在这些情况下,快照并不是可行的选择。

  AOF(Append Only File - 仅追加文件) 它的工作方式非常简单:每次执行 修改内存 中数据集的写操作时,都会 记录 该操作。假设 AOF 日志记录了自 Redis 实例创建以来 所有的修改性指令序列,那么就可以通过对一个空的 Redis 实例 顺序执行所有的指令,也就是 「重放」,来恢复 Redis 当前实例的内存数据结构的状态。

  redis4.0 的混合持久化

  Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是 自持久化开始到持久化结束 的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小

  

   于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

13、RDB 和 AOF 各自优缺点?

RDB 优点:

  1. 只有一个文件dump.rdb方便持久化
  2. 容灾性好,一个文件可以保存到安全的磁盘
  3. 性能最大化fork 子进程来完成写操作,让主进程继续处理命令,所以使 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 Redis 的高性能
  4. 相对于数据集大时,比 AOF 的 启动效率 更高

 RDB 缺点:

  1. 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 Redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候

AOF 优点:

  1. 数据安全,aof 持久化可以配置appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次
  2. 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题
  3. AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall),即将相同命令合并和压缩。

AOF 缺点:

  1. AOF 文件比 RDB 文件大,且 恢复速度慢
  2. 数据集大 的时候,比 rdb 启动效率低

第四篇、其他问题

14、redis 如何实现分布式锁?

  请参考我的博客:redis-13 实现分布式锁

15、redis 过期键的删除策略是什么?redis 对溢出键是怎么删除的?

  请参考我的博客:redis-11 redis清除过期 key 详解

16、redis 中有 1 亿 key,其中有 10w 个 key 是以某个固定已知前缀开头,如何将它们全部找出来?

  使用 keys 指令可以扫出指定模式的 key 列表。但是要注意 keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

 
原文地址:https://www.cnblogs.com/liang1101/p/12920590.html