侧边栏壁纸
博主头像
Lin2J博主等级

升级了服务器,访问应该会更加流畅🇨🇳

  • 累计撰写 99 篇文章
  • 累计创建 43 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Redis 面试题 40 问

Lin2J
2021-10-27 / 0 评论 / 0 点赞 / 444 阅读 / 9,136 字 / 正在检测是否收录...
1. 什么是 Redis

Redis 是一个基于内存的高性能键值对数据库,可以处理 10w QPS 的操作。

比较 Memcached,Redis支持更多的数据结构,因此 Redis 可以用来实现更多的功能。

Redis 最大的缺点是数据库的容量受到物理内存的限制,不适合做海量数据存储。

2. Redis 有哪些优缺点

优点:

  1. 速度很快,所有的操作都在内存中进行。键值对的数据结构使得查找和插入的时间复杂度都是$O(1)$;
  2. 数据类型丰富,Redis 提供了丰富的数据类型可以在不同的场景下使用;
  3. 支持事务,Redis 的操作都是原子性操作,但是事务是非原子性的;
  4. 丰富的特性,可以作为缓存,消息队列,按 key 设置过期时间,过期后自动删除。

缺点:

  1. 容量受到物理内存的限制,不能作为海量数据存储;
  2. 如果要进行完整重同步,需要生成 rdb 文件,并进行传输。在这个过程会占用 CPU 和网络带宽;
  3. 修改配置后,需要重启。重启的过程需要从硬盘加载数据,时间比较久。
3. 为什么要使用 Redis 作为缓存

Redis 的操作都在内存中进行,速度很快,可以支持 8w/s TPS 和 10w/s QPS。

Redis 基于键值对的操作,使得查找和插入的时间复杂度为 $O(1)$。

Redis 可以对缓存设置过期时间,过期的数据自动清理,可以设置不同的清理策略。

QPS:应用系统每秒能处理的最大请求数量(一次请求指从请求到处理完返回)。

TPS:应用系统每秒能处理的最大事务次数。

4. Redis 有哪些淘汰策略
  1. noeviction:不淘汰,如果达到最大内存,返回错误信息;
  2. allkeys-lru:在所有的 key 中,找出最近最少使用的(less recently used)淘汰;
  3. volatile-lru:在过期的 key 中,找出最近最少使用的(less recently used)淘汰;
  4. allkeys-random:在所有的 key 中,随机淘汰数据;
  5. volatile-random:在过期的 key 中,随机淘汰数据;
  6. volatile-ttl:从已设置过期时间的 key 中,找出剩余存活时间(time to live)最短的淘汰

配置淘汰策略,在配置文件有一行:

# maxmemory-policy volatile-lru

5. Redis 为什么那么快
  1. 完全基于内存,绝大部分的操作都是内存中进行。键值对的数据结构使得查找和操作的时间复杂度为$O(1)$;
  2. 采用单进程单线程,避免不必要的上下文切换和对锁的竞争带来的性能消耗;
  3. 使用多路 $I/O$ 复用模型,而非阻塞 $I/O$;
  4. 底层模型;

多路 $I/O$ 复用模型的介绍:https://zhuanlan.zhihu.com/p/63179839

6. Redis 的数据类型有哪些(todo 使用场景)
  1. string:最基本的数据类型,二进制安全的字符串,最大 512M;
  2. list:按照添加的顺序保持顺序的字符串列表;
  3. set:无序的字符串集合;
  4. zset:sorted set,已排序的字符串集合;
  5. hash:key-value 的一种集合。
7.Redis 有几种持久化方式

两种持久化方式

RDB:将数据库中的数据持久化到一个二进制文件 dump.rdb 的文件,定时保存;

AOF:将 Redis 执行过的命令作为一个命令集合,保存在一个 appendonly.aof 文件中。

Redis 默认的持久化方式是 RDB。

当 Redis 重启的时候,会优先使用 AOF 来恢复数据,这样恢复的数据更加完整。

8. RDB 的运行原理以及配置

Redis 在进行 RDB 持久化操作时,会 fork 一个子进程。

子进程用来将内存中的数据写入到临时的 RDB 文件。

等待写入完成后,再替换原来的 RDB 文件,这样可以实现 copy-on-write。

RDB 可以设置多个时间点的备份,这样可以很方便地恢复到不同时间点的状态。

RDB 可以在 redis.conf 文件中配置

save 60 1000

每隔 60 秒,如果有超过 1000 个 key 发生变更,就生成新的 dump.rdb 文件,这个文件也叫快照。

像这样的配置可以设置多个,这样可以生成多个快照。

9. AOF 的运行原理以及配置

AOF 持久化默认是关闭的,但是生产环境一般都要打开

appendfsync yes

打开 AOF 之后,Redis 的每一条命令,都会被保存到日志中。

当 Redis 会先将命令写入系统缓存中,然后通过 fsync 策略写入日志文件。

AOF 的 fsync 策略有三种,

appendfsync everysec # 每一秒同步一次,是默认的方式,这样最多丢失一秒的数据
appendfsync always # 每执行一条命令就保存一次,性能差,吞吐量低
appendfsync no # 只是将命令写入系统缓存,没有保存到日志文件

10. AOF 的 rewrite 机制

为了避免 appendonly.aof 文件不断膨胀,Redis 会在后台定时做 rewrite 操作。

Redis 根据当前内存的数据重新构建一个最新的日志文件,写入到新的 AOF 文件中。

最后用新的 AOF 文件替换旧的 AOF 文件。

AOF 文件一直膨胀的原因是,随着时间的推移会有数据被删除,而这些数据的写入操作是记录在 AOF 文件中的。

rewrite 的配置项

auto-aof-rewrite-percentage 100 # 当前写入日志文件的大小超过上一次写入大小的 100% 时进行rewrite
auto-aof-rewrite-min-size 64mb # 当 AOF 文件超过这个大小,才会触发 rewrite。这个配置只有在第一次 rewrite 时才有效,后面就会根据这个配置了,而是根据上一次大小

11. 如何选择合适的持久化

如果能够接受短时间内的数据丢失,使用 RDB。

如果对数据的一致性要求高,使用 AOF,并配置 appendfsync always

牺牲性能来保证数据的强一致性。

12. Redis 的持久化数据和缓存怎么做扩容
  1. 如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。

  2. 如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。

哈希一致性算法:https://www.zsythink.net/archives/1182

13. Redis 如何做内存优化

内存优化主要围绕降低内存使用量进行。

  1. 缩减键和值的长度。

    键的定义能短则短;键对象在序列化时,避免存储无效属性,可以选择更加高效的序列化工具降低字节数组的大小,如 protostuff, kryo 等;

  2. 尽量使用整数对象以节省内存。

    Redis 内部维护了一个 $[0, 9999]$ 的整数对象池,因为创建大量的 redisObject 需要使用很多内存,每个 redisObject 的内部结构至少 16 字节。因此创建共享对象池以节约内存。

  3. 字符串优化;

  4. 编码优化;

  5. 控制 key 的数量。

Redis 内存优化的文章:https://cloud.tencent.com/developer/article/1162213

14. Redis 的线程模型。

事件分派器根据事件类型来选择对应的事件处理器进行处理。

线程模型例图(建议会画出来)
Redis线程模型

多个 Socket 可能会并发产生不同的操作,每个操作都是不同的文件事件。

$I/O$ 多路复用程序会监听多个 Socket,会将产生时间的 Socket 放入队列中。

事件分派器每次从队列中取出一个 Socket,根据 Socket 的事件类型选择对应的事件处理器进行处理。

使用多线程的目的是尽可能利用电脑的多核 CPU 能力提高并发行。

但是官方通过测试发现 Redis 的性能瓶颈不在 CPU ,而在于内存和网络带宽。

因此采用更简单的单线程,毕竟多线程会很多麻烦。

15. Redis 的事务

Redis 的事务本质上是一组命令的集合,支持一次性执行多个命令。(一次性)

一个事务的所有命令都会被序列化,在事务执行过程中,按照串行化的方式从队列中取出执行。(顺序性)

其他客户端提交的命令不会插入到事务执行命令队列中。(排他性)

Redis 中的单个命令是原子性的,但是事务不保证原子性,且没有回滚,事务中的一个命令失败了,其他命令依然会执行。(非原子性)

总结说:Redis 的事务是一次性、顺序性、排他性和非原子性的执行一个队列中的一系列命令。

16. Redis 事务的隔离级别

Redis 事务没有隔离级别的概念。

事务的命令集合在发送 EXEC 命令之前,都被缓存在队列中没有被执行。

因此,不会存在说一个事务看得到事务内的更新而其他事务看不到的情况。

17. Redis 事务相关的命令

Redis 事务的三个阶段:① 开始事务;② 命令入队;③ 执行事务

  1. WATCH:监视一个或者多个 key,如果在事务执行前,被监视的 key 被其他命令改变了,则事务被打断;
  2. MULTI:标记一个事务块的开始;
  3. EXEC:执行所有事务块的命令;(执行后,被 WATCH 的 key 将不会再被监视)
  4. DISCARD:取消事务,放弃事务中的所有命令;
  5. UNWATCH:取消 WATCH 对所有 key 的监控。
18. Redis 的集群方案

Redis 的集群方案有三种:主从模式、Sentinel 模式、Cluster 模式。

Redis 集群详解:https://blog.csdn.net/miss1181248983/article/details/90056960/

19. Redis 主从模式

主从模式是三种模式中最简单的,它将数据库分为两类:主数据库(master)和从数据库(slave)。

主数据库可以进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库。

从数据库只可以进行读操作,并接受从 master 同步过来的数据。

一个 master 可以拥有多个 slave,但是一个 slave 只对应一个 master。

master 挂了之后,集群只能读不能写;某个 slave 挂了不影响其他 slave 和 master 的工作。

master 挂了不会重新选出一个 master,因此这种模式虽然简单,但是可用性不高。

20. Redis 主从复制启动的方式
  1. 在从服务器中的配置文件增加:slaveof <masterip> <masterport>
  2. 在启动 redis-server 时,加入参数:--slaveof <masterip> <masterport>
  3. redis-server 启动之后,通过客户端命令:slaveof <masterip> <masterport>
    • 如果 master 启动了保护模式(protected-mode yes),从服务器还需要在使用 masterauth <masterip> <masterport>
21. Redis 主从复制的过程

主从复制的过程自从 slave 节点输入 slaveof 命令便开始运作

  1. 保存主节点信息,主从建立 socket 连接;
  2. slave 向 master 发送 ping 命令,以验证 master 的可用性和网络连通性;
  3. 权限验证。如果 master 有使用 requirepass 配置的话,需要使用 masterauth 进行验证;
  4. 同步数据集。slave 会把 master 的所有数据复制到内存中;(耗时最长)
  5. 命令持续复制。master 会持续地把写命令传递给从节点,以保证主从数据的一致性。
22. Redis 主从复制的好处
  1. 数据冗余,主从复制是持久化之外的一种冗余方式;
  2. 高可用,快速故障恢复。master 挂掉之后,可以由从节点提供服务,实现快速的故障恢复;
  3. 负载均衡,读写分离。在写少读多的情况下,可以通过多个从节点来提供 Redis 服务的并发量;
23. Redis Sentinel(哨兵)模式

主从模式的弊端就是 master 挂了之后,集群无法再对外提供写入操作。

sentinel 中文是哨兵,所以它的作用就是监控 Redis 集群的运行情况。

当 master 节点挂掉之后,sentinel 会从 slave 中找到一个节点作为新的 master,并修改它的配置文件。

sentinel 找到新的 master 之后,通过发布/订阅模式,通知其他 slave 的修改配置文件,比如将 slaveof 指向新的节点。

因为 sentinel 本身也有可能挂掉,所以 sentinel 也会启动多个形成一个 sentinel 集群,并互相监控。

24. Redis Sentinel 模式的工作机制

每个 sentinel 以每秒钟一次的频率向它所知的 master,slave 以及其他的 sentinel 发送一个 PING 命令。

如果一个实例最后一次有效 PING 回复的时间超过 down-after-milliseconds 的值,这个实例就会被 sentinel标记为主观下线。

当有足够数量的 sentinel 判定某个实例为主观下线时,会将实例标记为客观下线。

一般情况下,每个 sentinel 会以每 10 秒一次的频率向它已知的实例发送 INFO 命令。

当实例被标记为客观下线时,sentinel 会将发送频率从 10 秒一次改为 1 秒一次。

若没有足够数量的 sentinel 同意实例已经下线,则实例的客观下线状态会被移除。

当实例重新向 sentinel 的 PING 命令返回有效恢复时,实例的主观下线状态会被移除。 

sentinel monitor mymaster 192.168.30.128 6379 2 #判断master失效至少需要2个sentinel同意,建议设置为n/2+1,n为 sentinel 个数

25. Redis Cluster 模式

Sentinel 模式基本可以满足一般生产的需求,具备高可用性。

但是因为所有的实例保存的都是相同的数据,很浪费内存。

因此,Redis 加入了 Cluster 集群模式,可以实现分布式存储。

Cluster 模式没有使用一致性哈希,而是引入了哈希槽的概念。

Redis 设置了 16384 个哈希槽,每个 key 都通过 CRC16 校验后对 16384 取模来决定存储槽。

集群中的每一个节点都负责一部分的哈希槽,通过转移节点负责的槽位,可以实现在添加和删除节点时集群依旧处于可用状态。

存取 key 时,Redis 使用 CRC16 算法得出一个结果,然后拿这个结果对 16384 取模得到槽位的位置。

通过槽位可以找到负责这个槽位的节点进行存取操作。

哈希一致性算法 : https://www.zsythink.net/archives/1182

为什么是 16384 : https://www.cnblogs.com/youngdeng/p/12855424.html

26. Redis 是单线程,如何提高多核CPU的利用率

可以启动多个 Redis 实例,并通过 taskset 命令将实例绑定到特定的 CPU

27. Redis 分区的方案以及优缺点

分区是将数据分别存储到多个 Redis 实例的存储过程,每个实例只会保存 key 的一个子集。

分区的类型分为范围分区和哈希分区。

范围分区是最简单的分区方式,但是一般需要维持区间范围到实例的映射表,对 Redis 来说并不是好方法。

哈希分区是将 key 转换成一个数字,然后使用 hash 函数得到一个数字,再将这个数字进行取模运算确定分配到哪个实例。

分区的优点

  1. 允许利用多台计算机的内存值,构建更大的数据库;
  2. 允许通过多核和多台计算机,拓展计算能力;
  3. 允许通过多台计算机和网络适配器,拓展网络带宽;

分区的劣势

  1. 不支持同时操作多个 key,因为这些 key 不一定在同一台服务器上;
  2. 数据的备份和恢复变得复杂;
28. Redis 实现分布式锁

Redis 实现分布式锁用到 Redis 的原生命令 set key value ex timeout nx 或者setnx key value

nx 表示 if not existex timeout 表示该键值对能存活的时间,单位:秒。

29. Redis 如何解决并发竞争 key 的问题
  1. 使用 WATCH 命令做乐观锁。被监视的 key 如果不是原来的值,命令就会被拒绝执行;
  2. 使用分布式锁,操作 key 之前先去申请该 key 的分布式锁,拿到锁才能操作;
  3. 将 key 和时间戳做关联,操作时当前的时间戳要比 key 的时间戳大才能执行操作;
  4. 使用消息队列。在并发量很大的时候,使用消息队列做串行化处理。
30. Redis 缓存异常
  1. 缓存击穿

    指某一个 key 值的请求量非常大,在并发量非常大的时候,key 失效了,大量请求落到了数据库上。

    解决方法是设置热点数据不过期。

  2. 缓存穿透:

    指某一个 key 值是不可能存在的,恶意分子不断请求该 key,使得请求不断地落在数据库上。

    解决方法是在业务层对不合法参数进行拦截或者使用布隆过滤器判断数据在不在数据库中。

  3. 缓存雪崩:

    指某一时刻,有大量的缓存同时到期,key 失效后,大量的请求落到了数据库上。

    解决方法是热点数据设置不过期或者数据设置过期时使用随机数作为过期时间。

31. Redis 的客户端

Jedis:是老牌的Redis的Java实现客户端,提供了比较全面的Redis命令的支持,

Redisson:实现了分布式和可扩展的Java数据结构。

Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

优点:

Jedis:比较全面的提供了Redis的操作特性

Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列

Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操 作单个Lettuce连接来完成各种操作

32. Redis 常见的性能问题和解决方案
  1. Master 最好不要做任何持久化工作,如 RDB 内存快照和AOF文件

  2. 如果数据比较重要,某个 AOF 开启AOF备份,每秒钟备份一次。

  3. 为了主从复制的速度和稳定性,Master 和Slave最好在同一个局域网内

  4. 尽量避免在压力很大的主库上增加从库

  5. 主从复制不要用图状结构,用单向链表更为稳定,即 Master <- Slave1 <- Slave2 <- Slave3…

    这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以很快启用 Slave1 作为新的 Master,其他不做更改

33. Redis 如何实现异步队列

使用 list 数据结构,插入时使用 lpush 或者 rpush ,然后弹出时使用 brpop 或者 blpop

brpopblpop 在没有数据时会进入阻塞。

如果阻塞时间过长,Redis 和客户端因为长时间闲置而断开连接,这个时候需要在代码里捕捉异常,然后进行重新连接。

34. Redis 如何实现延时队列

使用 zset 数据结构,用时间戳作为score,使用 zadd key score1 value1 命令生产消息,使用 zrangebysocre key min max withscores limit 0 1消费消息最早的一条消息。

35. Redis 回收进程如何工作

参考缓存过期删除策略。

36. Redis 与 Mencached 的区别
  1. 数据结构:Redis 相比 Memcached 拥有丰富的数据结构以适用不同使用场景;
  2. 持久化:Redis 有持久化功能,能快速恢复热点数据;
  3. 高可用:Redis 本身支持集群,可以主从复制、读写分离,而 Memcached 需要进行二次开发。
  4. 存储的内容比较大:Mencache 存储的 value 最大为 1M,而 Redis 的 value 最大是 1GB。
  5. CPU:Redis 使用的是单核,Memchached 可以使用多核。

Redis 作者的看法:https://stackoverflow.com/questions/2873249/is-memcached-a-dinosaur-in-comparison-to-redis

37. Redis 中有一亿个 key,其中有 10w 个以某个固定前缀开头的 key,如何将他们找出来

使用 keys 命令匹配 指定 pattern 的 key。

但是 keys 会阻塞线程,因为 Redis 是单线程的,所以如果是线上系统,不建议用这个命令。

可以使用 scan 命令,它可以无阻塞地取出指定模式的 key 列表, 但是有可能重复(后续服务层去重),耗时也比较长。

整体上 scan 花费的时间会比 keys 的长。

38. Redis如何做大量数据插入

使用常用的 set 命令会有大量的时间浪费在每一个命令的往返时间上。

官方在2.6版本推出了一个新的功能 -pipe mode,即将支持Redis协议的文本文件直接通过 pipe 导入到服务端。

将写满 Redis 命令的文本文件,转化成 Redis Protocol ,最后利用管道连接符插入。

cat data.txt | redis-cli --pipe

官方文档:https://redis.io/topics/mass-insert

39. 如何保存缓存和数据库双写时的数据一致性

无非是先删除缓存和先删除数据库信息的问题。

第一种,先删除缓存,那么假设在更新数据库的时候,另一个线程读取缓存读不到,就会去数据库查询数据然后更新缓存。这时先前的线程才更新完成数据库,就会造成缓存里的还是旧值的情况。

第二种,先删除数据库,其他数据在查询时,因为缓存还有数据,所以不会查询数据库。等到更新完数据库再把缓存中的数据更新,这是缓存就正常了。

第一种产生了长时间的脏读,第二种只有很短时间的脏读。

第一种删除缓存后有可能造成缓存击穿,如果大量线程访问就会造成数据库访问过大,第二种其他线程会读取缓存数据,不会对数据库造成太大压力,更新数据库后缓存就马上更新了。

40. Redis 是如何判断 key 过期的然后删除的

首先介绍三种删除策略

  1. 定时删除

    在添加 key 的时候,如果有设置过期时间,就创建一个定时器给它,让定时器在过期时间来临时将 key 删除。

    优点:保证内存被尽快释放。

    缺点:创建大量的定时器会占用 CPU 资源,影响性能。

  2. 懒汉式删除

    key 过期时不删除,每次获取 key 的时候去检查是否过期,若过期则删除。

    优点:占用 CPU 的时间比较少。

    缺点:大量 key 超时后未删除,会造成内存泄漏。

  3. 定期删除

    每隔一段时间执行一次删除过期 key 的操作。

    优点:可以通过限制删除的时长和频率,来减少对 CPU 的占用时间,也可以避免超时 key 大量堆积导致内存泄漏。

    缺点:内存释放不如定期删除,CPU占用不如懒汉式删除,属于比较综合的做法。

Redis 采用的是懒汉式删除 $+$ 定期删除的方式

在获取 key 的时候,判断 key 是否过期,过期则删除;

平时也会有定期的检查每个数据库中的 key ,判断是否过期,但是会限制频率和每次的工作时长。

Redis 过期策略及实现原理:http://www.jiangxinlingdu.com/redis/2019/01/31/delcore.html

0

评论区