跳到主要内容

redis 相关面试题归总

img

redis 是一个高性能的 key-value 数据库,它是完全开源免费的,而且 redis 是一个 NOSQL 类型数据库,是为了解决高并发、高扩展,大数据存储等一系列的问题而产生的数据库解决方案,是一个非关系型的数据库。

1 redis 集群如何进行故障转移?如何保证高可用?主从同步的过程? Rdb 是什么?

Redis集群的故障转移、高可用性和主从同步是其关键特性,以下是这些特性的详细解释:

  1. Redis集群的故障转移

Redis集群的故障转移主要依赖于Redis Sentinel(哨兵)或Redis Cluster。

  • 基于Redis Sentinel的故障转移
    • Sentinel通过定期向主节点和从节点发送PING命令来监控它们的健康状态。
    • 当主节点连续几次未能响应PING命令时,Sentinel将主节点标记为“主观下线”(SDOWN)。
    • 当足够数量的Sentinel(由配置文件中的quorum参数指定)都标记主节点为SDOWN时,主节点会被标记为“客观下线”(ODOWN),并从其中一个从节点中选择一个作为新的主节点。
  • 基于Redis Cluster的故障转移
    • Redis Cluster中的每个节点都负责监测与其相连的其他节点的健康状况,通过互相发送PING命令。
    • 当某个节点检测到与其相连的主节点故障时,它会启动故障转移过程,并尝试将某个从节点提升为主节点。
  1. 如何保证高可用

Redis通过主从复制和集群技术来保证高可用。

  • 主从复制:通过将一个主节点的数据复制到多个从节点,实现数据的冗余备份。当主节点故障时,从节点可以接管服务,确保服务不中断。
  • Redis Sentinel:除了监控和故障转移外,Sentinel还提供了配置管理和通知功能,进一步提高了Redis集群的可用性。
  • Redis Cluster:通过将数据分布在多个节点上,实现了水平扩展和高可用。即使部分节点故障,集群也能继续提供服务。
  1. 主从同步的过程

主从同步的过程主要包括全量同步和增量同步。

  • 全量同步
    • 从节点连接主节点,发送SYNC命令。
    • 主节点执行BGSAVE命令生成RDB文件,并使用缓冲区记录在此期间执行的写命令。
    • 主节点将RDB文件发送给从节点,并在发送期间继续记录写命令。
    • 从节点加载RDB文件,并执行主节点发送的写命令,完成数据同步。
  • 增量同步:在全量同步完成后,主节点会将新的写命令实时发送给从节点,保持数据的实时更新。
  1. RDB是什么

RDB是Redis的一种持久化方式,也称为快照持久化。它是将当前内存中的数据集快照写入磁盘,保存为RDB文件。当Redis重新启动时,可以读取RDB文件恢复数据。RDB的触发条件可以是手动触发或自动触发(如根据配置的时间间隔或数据变更量)。RDB的优点是恢复速度快,但可能会丢失故障发生后的数据。

2 Redis 的大 key 怎么应对?

Redis中的大key(BigKey)是指包含大量元素的复合数据类型,如列表、哈希、集合或有序集合等,它们的大小可能达到数MB甚至数百MB。大key在Redis中可能引发多种问题,如内存消耗过多、网络拥塞、服务延迟等。以下是一些建议的应对策略:

  1. 识别大key: 首先需要识别出 Redis 中的大key。可以使用redis-cli工具的--bigkeys选项来扫描并识别出大key。
  2. 优化数据结构: 检查并优化存储在大key中的数据结构。例如,如果一个哈希(Hash)类型的key包含大量字段,可以考虑将其拆分成多个key;如果一个列表(List)类型的key包含大量元素,可以考虑使用有序集合(Sorted Set)或其他更合适的数据结构。
  3. 拆分大key: 如果可能的话,将大key拆分成多个小key。这可以通过将大key的value按照一定的规则拆分开来,然后使用多个key来存储这些拆分后的value。在拆分时,要确保数据的完整性和一致性。
  4. 压缩value: 对于大key的value,如果其数据类型支持,可以使用序列化技术进行压缩处理。这样可以缩小value的空间占用,从而减少内存消耗。
  5. 设置过期时间: 为大key设置合理的过期时间,以便在数据过期后自动删除,避免大key长时间占用内存。但需要注意的是,过期检查可能会带来一定的性能开销。
  6. 监控与报警: 通过监控Redis的内存使用情况和大key的变化情况,及时发现并处理潜在的问题。可以设置报警阈值,当大key的大小超过一定阈值时触发报警,以便及时采取措施。
  7. 限流与缓存: 在应用层对大key的访问进行限流,避免同时有大量请求访问同一个大key导致性能问题。同时,可以在Redis客户端开启本地缓存,减少对Redis的访问压力。
  8. 删除或替换大key: 如果大key不再需要,或者其数据可以被其他方式替代,可以考虑直接删除大key或者将其替换成一个小key。但需要注意的是,删除大key可能会带来一定的性能开销,并且需要确保删除操作不会对其他业务造成影响。
  9. 使用Redis集群: 如果单个Redis实例无法处理大key带来的性能问题,可以考虑使用Redis集群来水平扩展处理能力。Redis集群可以将数据分布在多个节点上,从而减轻单个节点的压力。
  10. 定期审计和清理: 定期审计Redis中的数据,识别并清理不再需要的大key和过时数据。这有助于保持Redis的性能和可用性。

3 redis 如何做延时队列吗?

Redis 本身并不直接支持延时队列(Delayed Queue)的功能,但你可以通过结合其数据结构(如有序集合 sorted set)和特性(如键过期和发布/订阅)来实现延时队列的功能。以下是一些常见的方法来实现 Redis 延时队列:

方法一:使用有序集合(Sorted Set)

你可以将任务作为有序集合的成员,将任务的执行时间作为分数(score)。然后,你可以使用一个循环任务(如使用定时任务框架如 cron、Celery 的周期性任务等)来定期检查有序集合中最早到期的任务,并执行它。

步骤如下:

  1. 当一个延时任务被添加时,将其作为一个成员添加到有序集合中,并将任务的执行时间戳作为分数。
  2. 使用一个循环任务来检查有序集合中最早到期的任务(即分数最小的成员)。
  3. 如果当前时间已经超过了最早到期的任务的执行时间,则从有序集合中移除该任务并执行它。
  4. 重复上述步骤,直到没有到期任务为止。

方法二:使用键过期和发布/订阅

另一种方法是使用 Redis 的键过期和发布/订阅功能。

步骤如下:

  1. 当一个延时任务被添加时,将其详细信息存储在一个字符串或哈希类型的键中,并设置该键的过期时间为任务的执行时间。
  2. 同时,为该键设置一个过期事件的监听器。当键过期时,监听器会发布一个消息到指定的频道(channel)。
  3. 创建一个订阅者来监听该频道。当收到消息时,订阅者从 Redis 中获取任务的详细信息并执行它。

为了实现键过期事件的监听器,你可能需要使用 Redis 的键空间通知(Keyspace Notifications)功能,或者通过一些外部工具或框架(如 Lua 脚本、Redis 触发器、Redis 客户端库等)来实现。

方法三:使用第三方库或工具

除了上述手动实现方法外,还有一些第三方库或工具可以帮助你更容易地实现 Redis 延时队列。例如,redis-delayed-jobbull(基于 Redis 的队列库,支持延时队列)等都是不错的选择。这些库或工具通常提供了更高级别的抽象和更丰富的功能,可以简化延时队列的实现过程。

注意事项

  • 确保 Redis 服务器的性能和稳定性。延时队列可能会产生大量的读写操作和定时任务,因此需要对 Redis 服务器进行适当的优化和监控。
  • 根据实际情况选择合适的实现方法。不同的实现方法有不同的优缺点和适用场景,需要根据具体需求来选择最适合的方法。
  • 考虑并发和竞争条件。在多个任务同时到达或执行时,需要确保任务的正确性和顺序性。可能需要使用锁或其他同步机制来避免并发问题。

4 redis 支持哪几种数据类型?redis 的特点?

String、List、Set、Sorted Set、hashes

Redis 本质上是一个 Key-Value 类型的内存数据库,很像 memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘上进行保存。因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value DB。

Redis 的出色之处不仅仅是性能,Redis 最大的魅力是支持保存多种数据结构,此外单个 value 的最大限制是1GB,不像 memcached 只能保存1MB的数据,因此 Redis 可以用来实现很多有用的功能,比方说用他的 List 来做 FIFO 双向链表,实现一个轻量级的高性 能消息队列服务,用他的 Set 可以做高性能的 tag 系统等等。另外 Redis 也可以对存入的 Key-Value 设置 expire 时间,因此也可以被当作一 个功能加强版的memcached来用。

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

5 使用 redis 有哪些好处?

  • 速度快,因为数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1)

  • 支持丰富数据类型,支持string,list,set,sorted set,hash

String

常用命令 :set/get/decr/incr/mget等;

应用场景 :String 是最常用的一种数据类型,普通的 key/value 存储都可以归为此类;常规计数,微博数,粉丝数等。

实现方式:String 在 redis 内部存储默认就是一个字符串,被 redisObject 所引用,当遇到 incr、decr 等操作时会转成数值型进行计算,此时 redisObject 的encoding字段为int。String 数据结构是简单的Key-Value类型,value不仅可以是String,也可以是数字。

**数据结构:**内部结构实现上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,如图所示:

image-20240512220946015

Hash

常用命令 :hget/hset/hgetall等

应用场景 :我们要存储一个用户信息对象数据,其中包括用户ID、用户姓名、年龄和生日,通过用户ID我们希望获取该用户的姓名或者年龄或者生日;存储用户信息,商品信息等等。例如修真院的首页的职业信息,只是简单的信息集合,我们可以直接将它储存到Redis中,在读取的过程中就不用序列化对象,直接操作。

实现方式:Redis 的 Hash 实际是内部存储的 Value 为一个 HashMap,并提供了直接存取这个 Map 成员的接口。如图所示,Key 是用户ID, value是一个Map。这个Map 的key是成员的属性名,value是属性值。这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field),也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据。当前HashMap的实现有两种方式:当HashMap的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的 HashMap 结构,这时对应的value的redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时redisObject的encoding字段为int。

**数据结构:**Redis Hash 通过分桶的方式解决 hash 冲突。它是无序字典。内部实现结构是同样的数组 + 链表二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。第一维是数组,第二维是链表。数组中存储的是第二维链表的第一个元素的指针。

image-20240512221737450

List

常用命令 :lpush/rpush/lpop/rpop/lrange等;

应用场景 :Redis list的应用场景 非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现;

实现方式:Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

**数据结构:**Redis 的列表相当于 Java 语言中的 LinkedList,注意它是链表而不是数组。这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)。

list 的特点是:

  1. 有序
  2. 可以重复
  3. 右边进左边出或者左边进右边出,则列表可以充当队列
  4. 左边进左边出或者右边进右边出,则列表可以充当栈

image-20240512222457082

Set

常用命令 :sadd/spop/smembers/sunion等;

应用场景 :Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且set提供了判断某个成员是否在一个set 集合内的重要接口,这个也是list所不能提供的;

  1. 共同好友、二度好友
  2. 利用唯一性,可以统计访问网站的所有独立 IP
  3. 好友推荐的时候,根据 tag 求交集,大于某个 threshold 就可以推荐

实现方式:set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是通过计算 hash 的方式来快速排重的,这也是 set 能提供判断一个成员是否在集合内的原因。

数据结构: set 和字典非常类似,其内部实现就是上述的 hashTable 的特殊实现,与字典不同的地方有两点:

  1. 只关注key值,所有的value都是NULL。
  1. 在新增数据时会进行去重。

image-20240512223452684

Sorted Set

常用命令 :zadd/zrange/zrem/zcard等;

应用场景 :Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。

实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

img

6 redis 为什么那么快?

  1. 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1)
  2. 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
  3. 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  4. 用多路 I/O 复用模型,非阻塞 IO;
  5. 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

7 缓存穿透是什么,如何解决?

缓存穿透 指查询一个一定不存在的数据,由于缓存是不命中时,需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求,都要到数据库去查询,进而给数据库带来压力。

解决方案:

  1. 将空数据也缓存:占有一定的空间,可能带来短期的数据不一致。如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟,
  2. 使用布隆过滤器 bloom filter:是一种预防的方案,占用空间少、误差可控。将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。

8 什么是缓存雪崩,如何解决?

缓存雪崩 是指在某一个时间段,缓存集中过期失效。当某一个时刻出现大规模的缓存失效的情况,那么就会导致大量的请求直接打在数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果运维马上又重启数据库,马上又会有新的流量把数据库打死。这就是缓存雪崩。

解决方案

  1. 过期时间设置随机值: 在原有的失效时间上加上一个随机值,比如,1-5 分钟随机。这样就避免了同一时间大量数据过期现象的发生而导致缓存雪崩。
  2. 分布式部署且均匀分布热点数据: 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。同时,分布式集群可以防止Redis宕机导致缓存雪崩的问题。
  3. 热点数据永不过期: 设置热点数据永远不过期。
  4. 使用熔断机制。当流量到达一定的阈值时,就直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上。至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。
  5. 提高数据库的容灾能力,可以使用分库分表,读写分离的策略。

9 造成缓存雪崩的原因是什么?

造成缓存雪崩 的关键在于在同一时间大规模的key失效。出现这个问题有下面几种可能:

  • 第一种可能是Redis宕机,

  • 第二种可能是采用了相同的过期时间。

10 什么是缓存击穿,如何解决?

某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。

解决方案:

  1. 加互斥锁。在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。
  2. JVM 锁保证了在单台服务器上只有一个请求走到数据库,通常来说已经足够保证数据库的压力大大降低,同时在性能上比分布式锁更好。 需要注意的是,无论是使用“分布式锁”,还是“JVM 锁”,加锁时要按 key 维度去加锁。

11 聊聊Redis 事务机制?

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

Redis 执行事务的流程如下:开始事务(MULTI)、命令入队、执行事务(EXEC)、撤销事务(DISCARD )。

12 在生成 RDB 期间,Redis 可以同时处理写请求么?

可以的,Redis 提供两个指令生成 RDB,分别是 save 和 bgsave。

如果是save指令,会阻塞,因为是主线程执行的。

如果是bgsave指令,是fork一个子进程来写入RDB文件的,快照持久化完全交给子进程来处理,父进程则可以继续处理客户端的请求。

13 如何选择合适的持久化方式?

一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性,你应该同时使用两种持久化功能。在这种情况下,当 Redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用 RDB 持久化。有很多用户都只使用 AOF 持久化,但并不推荐这种方式,因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用 RDB 还可以避免 AOF 程序的bug。

如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。

14 什么是缓存预热?

缓存预热 就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决方案

  1. 直接写个缓存刷新页面,上线时手工操作一下;
  2. 数据量不大,可以在项目启动的时候自动进行加载;
  3. 定时刷新缓存;

15 Redis是单线程还是多线程?

Redis6.0 采用多线程 IO,不过命令的执行还是单线程的。Redis6.0 之前,IO 线程和执行线程都是单线程的。

16 Redis key 的过期时间和永久有效分别怎么设置?

分别是 EXPIRE 和 PERSIST 命令进行设置。

17 热 Key 重键问题如何解决?

加锁重键(互斥锁):

热键不过期:在缓存中创建一个时间戳,先判断时间戳是否过期,如果没有过期返回原数据,过期了则访问数据源。

18 什么是布隆过滤器?

布隆过滤器是一个叫“布隆”的人提出的,它本身是一个很长的二进制向量,既然是二进制的向量,那么显而易见的,存放的不是 0,就是 1。布隆过滤器是一种由位数组和多个哈希函数组成概率数据结构,返回两种结果可能存在和一定不存在。布隆过滤器里的一个元素由多个状态值共同确定。位数组存储状态值,哈希函数计算状态值的位置。

**优点:**由于存放的不是完整的数据,所以占用的内存很少,而且新增,查询速度够快;

缺点: 随着数据的增加,误判率随之增加;无法做到删除数据;只能判断数据是否一定不存在,而无法判断数据是否一定存在。

19 Redis 的Hash冲突怎么办?

Redis 作为一个 K-V 的内存数据库,它使用用一张全局的哈希来保存所有的键值对。这张哈希表,有多个哈希桶组成,哈希桶中的 entry 元素保存了 key 和 value 指针,其中 key 指向了实际的键,value 指向了实际的值。

image-20240512224553337

所谓的哈希冲突通是指过不同的key,计算出一样的哈希值,导致落在同一个哈希桶中。

Redis 为了解决哈希冲突,采用了链式哈希。链式哈希是指同一个哈希桶中,多个元素用一个链表来保存,它们之间依次用指针连接。

image-20240512225328329

因为哈希冲突链上的元素只能通过指针逐一查找再操作,所以当往哈希表插入数据很多,冲突也会越多,冲突链表就会越长,那查询效率就会降低了。为了保持高效,Redis 会对哈希表做rehash操作,也就是增加哈希桶,减少冲突。为了rehash更高效,Redis还默认使用了两个全局哈希表,一个用于当前使用,称为主哈希表,一个用于扩容,称为备用哈希表。

20 说说 Redis 哈希槽的概念?

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

21 怎么实现Redis 的高可用?

我们在项目中使用 Redis,肯定不会是单点部署 Redis 服务的。因为,单点部署一旦宕机,就不可用了。为了实现高可用,通常的做法是,将数据库复制多个副本以部署在不同的服务器上,其中一台挂了也可以继续提供服务。Redis 实现高可用有三种部署模式:主从模式,哨兵模式,集群模式。

22 Redis主从复制的原理?

主从模式中 Redis 部署了多台机器,有负责读写操作主节点和只负责读操作从节点,从节点的数据来自主节点,实现原理就是主从复制机制。主从复制包括全量复制,增量复制两种。一般当 slave 第一次启动连接 master,或者认为是第一次连接,就采用全量复制,全量复制流程如下:

image-20240512230807681

  1. slave 发送 sync 命令到 master。
  2. master 接收到 SYNC 命令后,执行 bgsave 命令,生成 RDB 全量文件。
  3. master 使用缓冲区,记录 RDB 快照生成期间的所有写命令。
  4. master 执行完 bgsave 后,向所有 slave 发送 RDB 快照文件。
  5. slave 收到 RDB 快照文件后,载入、解析收到的快照。
  6. master 使用缓冲区,记录 RDB 同步期间生成的所有写的命令。
  7. master 快照发送完毕后,开始向 slave 发送缓冲区中的写命令;
  8. salve 接受命令请求,并执行来自 master 缓冲区的写命令

redis2.8版本之后,已经使用 psync 来替代 sync,因为 sync 命令非常消耗系统资源,psync 的效率更高。

slave 与 master 全量同步之后,master 上的数据,如果再次发生更新,就会触发增量复制。

当 master 节点发生数据增减时,就会触发 replicationFeedSalves() 函数,接下来在 Master 节点上调用的每一个命令会使用 replicationFeedSlaves() 来同步到 Slave节点。执行此函数之前呢,master 节点会判断用户执行的命令是否有数据更新,如果有数据更新的话,并且 slave 节点不为空,就会执行此函数。这个函数作用就是:把用户执行的命令发送到所有的 slave 节点,让 slave 节点执行。流程如下:

image-20240512231510089

23 什么是哨兵机制?

Redis 的哨兵 (sentinel) 系统用于管理多个 Redis 服务器,该系统执行以下三个任务:

  1. 监控 (Monitoring): 哨兵( sentinel ) 会不断地检查你的 Master 和 Slave 是否运作正常。
  2. 提醒 (Notification): 当被监控的某个 Redis 出现问题时, 哨兵 (sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
  3. 自动故障迁移(Automatic failover): 当一个 Master 不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效 Master 的其中一个 Slave 升级为新的 Master, 并让失效 Master 的其他 Slave 改为复制新的 Master; 当客户端试图连接失效的 Master 时, 集群也会向客户端返回新 Master 的地址, 使得集群可以使用新 Master 代替失效 Master。

24 哨兵机制的作用?

监控主数据库和从数据库是否正常运行。

主数据库出现故障时,可以自动将从数据库转换为主数据库,实现自动切换。

25 哨兵机制 (sentinel) 的高可用是如何实现?

当主节点出现故障时,由 Redis Sentinel 自动完成故障发现和转移,并通知应用方,实现高可用性。

其实整个过程只需要一个哨兵节点来完成,首先使用Raft算法(选举算法)实现选举机制,选出一个哨兵节点来完成转移和通知

26 哨兵核心点?

  1. 哨兵集群至少要 3 个节点,来确保自己的健壮性
  2. redis 主从 + sentinel 的架构,是不会保证数据的零丢失的,它是为了保证 redis 集群的高可用.

27 Redis 哨兵主备切换的时候会有数据丢失问题吗?

会有,主要考虑下面两种情况。

  1. 主从异步复制导致的数据丢失:redis master 和 slave 数据复制是异步的,这样就有可能会出现部分数据还没有复制到 slave 中,master 就挂掉了,那么这部分的数据就会丢失了
  2. 脑裂导致的数据丢失:脑裂其实就是网络分区导致的现象,比如,我们的 master 机器网络突然不正常了发生了网络分区,和其他的 slave 机器不能正常通信了,其实 master 并没有挂还活着好好的呢,但是哨兵可不是吃闲饭的啊,它会认为 master 挂掉了啊,那么问题来了,client 可能还在继续写 master 的呀,还没来得及更新到新的master 呢,那这部分数据就会丢失。

28 slave 到 master 选举算法?

如果一个 master 被认为宕机了,而且 majority多 数哨兵都允许了主备切换,那么某个哨兵就会执行主备切换操作,

此时首先要选举一个 slave 来,主要通过下面几个步骤:

  1. slave 跟 master 断开连接的时长(断开时间越短优先级越高)
  2. slave 优先级(在配置文件中的配置,slave priority 越低,优先级就越高。)
  3. 复制offset(哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高。)
  4. run id(如果上面两个条件都相同,那么选择一个run id 比较小的那个 slave)

img

29 介绍下 Redis Cluster ?

redis 从 3.0 开始支持集群功能。redis 集群采用无中心节点方式实现,无需 proxy 代理,客户端直接与 redis 集群的每个节点连接,根据同样的 hash 算法计算出 key 对应的 slot,然后直接在 slot 对应的 redis 节点上执行命令。在 redis 看来,响应时间是最苛刻的条件,增加一层带来的开销是redis不能接受的。因此,redis 实现了客户端对节点的直接访问,为了去中心化,节点之间通过 gossip 协议交换互相的状态,以及探测新加入的节点信息。redis 集群支持动态加入节点,动态迁移 slot,以及自动故障转移。

30 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?

redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

31 Redis 如何做内存优化?

可以好好利用 Hash, list, sorted set, set等集合类型数据,因为通常情况下很多小的 Key-Value 可以用更紧凑的方式存放到一起。尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面。

32 假如 Redis 里面有1亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用 keys 指令可以扫出指定模式的 key列表。

对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?

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

33 Redis 如何做大量数据插入?

Redis2.6 开始 redis-cli 支持一种新的被称之为 pipe mode 的新模式用于执行大量数据插入工作。

34 Redis 常见性能问题和解决方案?

  1. Master 最好不要做任何持久化工作,包括内存快照和 AOF 日志文件,特别是不要启用内存快照做持久化。
  2. 如果数据比较关键,某个 Slave 开启AOF备份数据,策略为每秒同步一次。
  3. 为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内。
  4. 尽量避免在压力较大的主库上增加从库
  5. Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF在重写的时候会占大量的 CPU 和内存资源,导致服务 load过高,出现短暂服务暂停现象。
  6. 为了 Master 的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master <– Slave1 <– Slave2 <– Slave3…,这样的结构也方便解决单点故障问题,实现 Slave 对 Master 的替换,也即,如果 Master 挂了,可以立马启用 Slave1 做 Master,其他不变。

35 如何解决 Redis 的并发竞争 Key 问题?

所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!

推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁,如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)。基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在 zookeeper 上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。

在实践中,当然是从以可靠性为主。所以首推Zookeeper。

36 简答描述 Redis 是实现分布式锁?

Redis 为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis 中可以使用 SETNX 命令实现分布式锁。

当且仅当 key 不存在,将 key 的值设为 value。若给定的 key 已经存在,则 SETNX 不做任何动作。其中SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。 返回值:设置成功,返回 1 。设置失败,返回 0 。

37 怎么保证缓存和数据库数据的一致性?

从理论上说,只要我们设置了合理的键的过期时间,我们就能保证缓存和数据库的数据最终是一致的。因为只要缓存数据过期了,就会被删除。随后读的时候,因为缓存里没有,就可以查数据库的数据,然后将数据库查出来的数据写入到缓存中。除了设置过期时间,我们还可以通过新增、更改、删除数据库操作时同步更新 Redis,可以使用事物机制来保证数据的一致性。一般有如下四种方案,具体如下:

  1. 先更新数据库,后更新缓存
  2. 先更新缓存,后更新数据库
  3. 先删除缓存,后更新数据库
  4. 先更新数据库,后删除缓存

第一种方案存在问题是:并发更新数据库场景下,会将脏数据刷到缓存。

第二种方案存在的问题是:如果先更新缓存成功,但是数据库更新失败,则肯定会造成数据不一致。

目前主要用第三和第四种方案。

38 Redis 内存淘汰策略有哪些?

Redis 的内存淘汰策略是指在 Redis 的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。 全局的键空间选择性移除:

noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。

allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)

allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。 设置过期时间的键空间选择性移除

volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除 近 少使用的key。

volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。

volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

39 reids 的过期键值的删除策略?

Redis 的过期键的删除策略是指当 Redis 中的缓存的 key 过期了,Redis要如何处理。

Redis中提供了三种删除策略:

1.定时删除

当放入数据后,设置一个定时器,当定时器读秒完毕后,将对应的数据从dict中删除。 优点: 内存友好,数据一旦过期就会被删除 缺点: CPU不友好,定时器耗费CPU资源,并且频繁的执行清理操作也会耗费CPU资源。用时间换空间 2.惰性删除 当数据过期的时候,不做任何操作。当访问数据的时候,查看数据是否过期,如果过期返回null,并且将数据从内存中清除。如果没过期,就直接返回数据。 优点: CPU友好,数据等到过期并且被访问的时候,才会删除。 缺点: 内存不友好,会占用大量内存。用空间换时间 3.定期删除 定期删除是定时删除和惰性删除的折中方案。每隔一段时间对redisServer中的所有redisDb的expires依次进行随机抽取检查。 Redis 中有一个 server.hz 定义了每秒钟执行定期删除的次数,每次执行的时间为250ms/server.hz。Redis中会维护一个current_db变量来标志当前检查的数据库。current_db++,当超过数据库的数量的时候,会重新从0开始。 定期检查就是执行一个循环,循环中的每轮操作会从current_db对应的数据库中随机依次取出w个key,查看其是否过期。如果过期就将其删除, 并且记录删除的key的个数。如果过期的key个数大于w25%,就会继续检查当前数据库,当过期的key小于w25%,会继续检查下一个数据库。当执行时间超过规定的最大执行时间的时候,会退出检查。一次检查中可以检查多个数据库,但是最多检查数量是redisServer中的数据库个数,也就是最多只能从当前位置检查一圈。

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

可以在同一个服务器部署多个 Redis 的实例,并把他们当作不同的服 务器来使用,在某些时候,无论如何一个服务器是不够的, 所以, 如果你想使用多个 CPU,你可以考虑一下分片(shard)。

41 为什么要做 Redis 分区?

分区可以让 Redis 管理更大的内存,Redis 将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使 Redis 的计算能力通过简单地增加计算机得到成倍提升,Redis 的网络带宽也会随着计算机和网卡的增加而成倍增长。

42 你知道有哪些 Redis 分区实现方案?

  1. 客户端分区就是在客户端就已经决定数据会被存储到哪个 redis 节点或者从哪个 redis 节点读取。大多数客户端已经实现了客户端分区。
  2. 代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy。
  3. 查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。

43 Redis 分区有什么缺点?

  1. 涉及多个 key 的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。
  2. 同时操作多个 key,则不能使用 Redis 事务。
  3. 分区使用的粒度是 key,不能使用一个非常长的排序 key 存储一个数据集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set)
  4. 当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。
  5. 分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。

img

内容收集于网络并整理:

1、Redis 面试题

2、面试不慌,史上最全 Redis 面试题(含答案

💡本文声明

转载请注明出处,谢谢合作!转载本文请声明原文章链接如下:

原文链接: https://zhoujun134.github.io/docs/面试相关/redis xiang-guan-mian-shi-ti-gui-zong

作者: Z 不殊

Z 不殊 致力于分享有价值的信息和知识。我们尊重并保护知识产权。本文仅代表作者观点,不代表任何立场。 如果本文有所侵权,请联系作者删除或修改!

Loading Comments...