Redis面试真题

月伴飞鱼 2024-08-24 14:26:23
面试题相关
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!

什么是Redis?

Redis是一个高性能的开源内存数据库系统,它使用键值对存储数据,并支持多种数据结构

  • 如字符串、哈希、列表、集合和有序集合。

与传统关系型数据库不同,Redis将数据存储在内存中,以实现快速读写操作。

  • 同时,它还提供持久化功能,可以将数据周期性地写入磁盘,以保证数据的持久性。

Redis有哪些使用场景?

Redis一般有以下几种使用场景:

缓存:

  • Redis最常见的使用场景是作为内存职业键值存储来构建缓存。

    • 由于Redis的性能非常高,可以处理大量的读写操作,因此非常适合用于缓存场景。
  • 例如,可以缓存从数据库查询出来的数据,后面再需要这些数据时

    • 可以直接从Redis中读取,而不需要再次进行数据库查询,从而提高性能。

消息队列系统:

  • Redis也可以作为消息队列使用。

    • PUB/SUB模型可以用来创建实时的消息系统。
  • 例如,一个电商网站,用户下订单后,可以把订单任务放入Redis的消息队列

    • 然后有专门的工作线程负责处理这些订单任务。

计数器:

  • Redis可以非常方便地实现计数器功能。

    • 比如用来记录网站的点击次数,或者用户的行为次数。
  • 例如,社交网络网站可以用Redis来记录用户发布的消息数量,或者用户被赞的次数。

实时系统:

  • 由于Redis的高性能特性,也经常被用于构建实时系统。

    • 比如实时统计用户的行为、游戏的实时排行榜等。
  • 例如,一个在线游戏,可以使用Redis来存储用户的分数

    • 然后使用Redis的排序功能,实时生成用户的排行榜。

Redis为什么这么快?

数据存储在内存:

Redis将数据存储在内存中,而内存的读写速度比磁盘快几个数量级。

  • 这使得Redis可以快速响应读写操作,适合于对延迟要求较高的应用场景。

单线程模型:

  • Redis采用单线程的方式来处理客户端请求。
    • 通过避免多线程之间的锁竞争和上下文切换,可以减少了不必要的开销,提高了处理效率。

精简的数据结构和高效算法:

  • Redis提供了多种数据结构(如哈希、集合、有序集合等)
    • 这些数据结构在内部经过精心优化,使用了高效的算法,以提高执行效率。

高效的网络通信:

  • Redis使用自己的协议进行网络通信(IO多路复用模型),协议简单且紧凑,减少了网络开销。
    • 此外,Redis还支持连接复用和连接池等机制,提高了网络通信的效率。

Memcached与Redis的区别都有哪些?

Memcached和Redis都是流行的内存缓存系统

  • 但它们在某些方面有一些区别,适用于不同的应用场景。

数据结构:

  • Memcached只支持键值对的存储,而Redis支持多种数据结构(字符串、哈希、列表、集合、有序集合等)
    • 使得Redis可以更灵活地应对不同的数据需求。

持久化:

  • Redis支持数据持久化,可以将数据写入磁盘,从而保证数据的持久性。
    • 而Memcached没有持久化功能,重启后数据会丢失。

数据查询和处理:

  • Redis提供了更多的数据查询和处理功能,例如按范围获取数据、排序、异步操作等。
    • Memcached则主要关注于高性能的读取和写入操作。

内存管理和性能:

  • Redis通过使用更复杂的数据结构以及多线程模式来提高内存使用效率和性能。
    • Memcached则更加简单,更专注于高速缓存。

根据这些区别,对于适合的应用场景来说:

  • 如果只需要简单的键值缓存和高速写入读取操作,可以选择Memcached
    • 它常用于缓存数据库查询结果、减轻数据库压力,或存储临时、短暂、快速失效的数据。
  • 如果需要更复杂的数据结构和功能,或者需要持久化数据,更好地支持数据分析和实时处理,可以选择Redis。
    • 它适用于具有实时计数、排行榜、发布/订阅消息等需求的应用,也可用作分布式锁等高级应用。

Redis常见的数据类型有哪些?

string 字符串

  • 字符串类型是 Redis 最基础的数据结构,首先键是字符串类型,而且其他几种结构都是在字符串类型基础上构建的。

  • 字符串类型实际上可以是字符串:

    • 简单的字符串、XML、JSON
    • 数字:整数、浮点数
    • 二进制:图片、音频、视频。
  • 使用场景:

    • 缓存、计数器、共享 Session、限速。

Hash(哈希)

  • 在 Redis中哈希类型是指键本身是一种键值对结构

    • value={{field1,value1},……{fieldN,valueN}}
  • 使用场景:

  • 哈希结构相对于字符串序列化缓存信息更加直观,并且在更新操作上更加便捷。

    • 所以常常用于用户信息等管理,但是哈希类型和关系型数据库有所不同,哈希类型是稀疏的
      • 而关系型数据库是完全结构化的,关系型数据库可以做复杂的关系查询
        • 而 Redis 去模拟关系型复杂查询开发困难且维护成本高。

List(列表)

  • 列表类型是用来储存多个有序的字符串,列表中的每个字符串成为元素,一个列表最多可以储存 2^32–1 个元素

  • 在 Redis 中,可以队列表两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引下的元素等

    • 列表是一种比较灵活的数据结构,它可以充当栈和队列的角色。
  • 使用场景:

    • Redis 的 lpush+brpop 命令组合即可实现阻塞队列,生产者客户端是用 lpush 从列表左侧插入元素
      • 多个消费者客户端使用 brpop 命令阻塞式的列表尾部的元素
        • 多个客户端保证了消费的负载均衡和高可用性。

ZSet的底层是如何实现的?

Redis的有序集合(Zset)是一种既可以看作是Set,又可以看作是Hash的数据结构

  • 其底层实现主要使用了哈希表和跳跃表。

哈希表:

  • 在Redis的Zset中,每个元素是唯一的,同时与每个元素关联的还有一个分数(Score)。
  • 元素的唯一性保证可以由哈希表来实现。
    • 即Redis的Zset将元素作为哈希表的键,将对应的分数作为哈希表的值。

跳跃表:

  • Redis的Zset还需要依据分数来对元素进行排序,这个特性是通过跳跃表来实现的。
    • 跳跃表是一种可以进行快速查找的数据结构。
    • 由于跳跃表的特性,对于分数的查找、插入、删除都具有较好的时间复杂度。

Zset的基本形状就是:

  • 哈希表确保了成员的唯一性和查找的速度,跳跃表确保了有序性以及区间查找。

什么是跳跃表?

跳跃表(Skip List)是一种可以替代平衡树的数据结构。

在它的简单形式中,跳跃表和链表是一致的,只不过在跳跃表中

  • 链表的每个节点可能有多个指向其后继的指针,这样可以实现快速查找等操作。

跳跃表最主要的特点是它的查找,插入,删除操作的时间复杂度都是O(logn)

基本思想:

假设我们有一个有序链表,我们想要查找一个元素,需要从头开始,一步一步的查找

  • 这种情况下查找的时间复杂度是O(n)

现在,我们对链表做一些增强,我们抽取出部分数据在上一级创建一个新的链表,上级链表的元素个数是下级链表的1/2

  • 同理,我们再在上级链表的基础上抽取出数据在更高级别创建链表,这样就构成了多级索引。
level3:  ---------------63
level2:  ----31---------63
level1:  ----31----47---63
level0:  15--31--47--63

如上,我们有一个包含4个元素{15,31,47,63}的链表

  • 我们在这个链表的基础上创建了三个更高级别的链表,最高级别的链表只包含一个元素。
  • 这样构成的数据结构就是跳跃表。

现在,如果我们想要查找47,我们可以从最高级别的链表开始,63>47

  • 所以我们到第二级别的链表查找,31<47,再向右走,到达63,63>47
  • 所以我们进入下一级链表,47就在这里。在跳跃表中,47这样的查找就是logn的复杂度。

跳跃表不仅能提供较快的查找速度,而且插入和删除元素也相对简单,主要是调整索引,改变指针的指向即可。

因此,跳跃表在很多场景下,例如Redis的Sorted Set,都有被广泛应用。

什么是缓存穿透?怎么解决?

缓存穿透是指在使用缓存系统时,恶意请求或者不存在的数据频繁地被发送到缓存中,导致缓存无法命中

  • 最终请求会直接落到后端数据库,造成数据库压力过大。

缓存穿透可能出现的原因包括:

恶意攻击:

  • 攻击者有意发送不存在的数据请求,试图使缓存失效,以达到影响系统性能或者触发系统错误的目的。

随机查询:

  • 大量并发的随机查询请求,其中大部分请求的数据都不存在于缓存中。

存在但很少访问的数据:

  • 一些数据很少被访问,经常被请求但却不存在于缓存中,导致缓存穿透。

为了解决缓存穿透问题,可以采取以下措施:

布隆过滤器(Bloom Filter):

  • 布隆过滤器是一种高效的数据结构,用于判断某个元素是否存在于集合中。

  • 在请求到来时,先使用布隆过滤器进行判断,如果被拦截则不再继续访问缓存和数据库,减轻了数据库的压力。

缓存空对象(Cache Null Object):

  • 当某个请求查询的数据不存在时,将空对象或者null放入缓存,以防止相同的请求频繁查询数据库。
  • 在一定时间内,如果有相同的请求再次到来,则直接从缓存中获取到空对象,避免了对数据库的重复查询。

数据预热(Cache Pre-warming):

  • 在系统启动时,将一些常用或重要的数据预先加载到缓存中
  • 提前热身缓存,避免了对这部分数据的缓存穿透问题。

异步加载(Asynchronous Loading):

  • 对于即将过期的缓存数据,可以在后台异步地进行数据加载和缓存的更新,避免了数据过期期间的缓存穿透。

限流和防护机制:

  • 通过对请求进行限流、IP白名单校验和请求验证等手段,防止恶意攻击和异常流量对缓存系统造成压力。

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

缓存雪崩是指在缓存中大量的缓存数据同时过期或者缓存服务器宕机,导致大量请求直接访问后端数据库

  • 造成数据库压力过大,甚至引发系统崩溃。

缓存雪崩可能出现的原因包括:

缓存数据同时过期:

  • 在某个时间点,大量的缓存数据同时过期,导致大量请求落到后端数据库。

缓存服务器宕机:

  • 缓存服务器突然宕机或者故障,导致所有请求无法访问缓存,直接访问后端数据库。

为了解决缓存雪崩问题,可以采取以下措施:

设置缓存失效时间的随机性:

  • 在设置缓存失效时间时,可以为不同的缓存设置不同的失效时间,以避免大量数据在同一时间内同时失效。
  • 可以在原有失效时间的基础上加上一个随机的时间,使得失效时间分散化。

使用热点数据永不过期:

  • 对于一些热点数据,可以将其缓存设置为永不过期
    • 以保证热点数据在任何时候都可以快速访问,避免因过期导致的缓存雪崩。

实时监控和预警:

  • 监控缓存系统的状态和数据过期情况,及时发现异常并采取相应的措施
    • 例如提前进行缓存的更新操作,或者在缓存失效前主动将其刷新。

备份缓存服务:

  • 部署多个独立的缓存服务器,以充分利用缓存的高可用性。
  • 如果一个缓存服务器出现故障,其他服务器仍然可以继续提供缓存服务。

数据预热:

  • 在系统启动时,将一些常用或重要的数据预先加载到缓存中,提前热身缓存,减少冷启动时缓存雪崩的风险。

限流和熔断机制:

  • 对缓存系统进行限流控制,可以设置最大并发数、最大请求时间等
    • 以及在缓存故障时启用熔断机制,防止大量请求直接落到后端数据库。

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

缓存击穿是指当缓存中没有某个key的数据,这当然会导致缓存无法命中,然后请求就会穿透缓存层,直接访问数据库。

  • 如果这个不命中的请求不止一个,而是成千上万个同时发生
  • 那么就会对数据库形成巨大的访问压力,可能会导致数据库访问瞬间崩溃。

最常见的缓存击穿场景就是有大量请求同时查询一个热点key

  • 但是此时缓存中该key的数据刚好过期,于是大量的请求就会直接穿透到数据库。

针对缓存击穿问题,常见的解决方案有:

设置热点数据永不过期

  • 这种方法适用于某些更新不频繁但是访问非常频繁的热点数据。

缓存数据过期时间设置随机

  • 防止同一时间大量数据过期现象发生。

使用互斥锁(Mutex key)

  • 对于同一个key,只允许一个线程去加载数据,其他线程等待加载完成直接使用即可。

服务降级与熔断

  • 如果数据库压力过大,可以暂时拒绝部分请求,让系统在承受的压力范围内运行。

布隆过滤器的原理是什么?它的优点是什么?缺陷是什么?

布隆过滤器(Bloom Filter)是一种数据结构,用于快速判断一个元素是否属于一个集合

它的原理、优点和缺陷如下:

原理

  • 哈希函数:

    • 布隆过滤器使用多个哈希函数(通常是非加密哈希函数),将输入元素映射成多个不同的位数组索引。
  • 位数组:

    • 布隆过滤器内部维护一个位数组,所有位的初始值都为0。
  • 添加元素:

    • 当要将一个元素添加到布隆过滤器中时,对该元素应用多个哈希函数,然后将相应位数组索引位置的位设置为1。
  • 查询元素:

    • 当要查询一个元素是否存在于布隆过滤器中时
      • 同样对该元素应用多个哈希函数,检查相应位数组索引位置的位是否都为1。
    • 如果所有位都为1,则可能存在;如果有任何一位为0,则一定不存在。

优点

  • 节省内存:

    • 相比于使用散列表或集合等数据结构,布隆过滤器占用的内存较少,因为它只需要维护位数组。
  • 快速查询:

    • 布隆过滤器的查询操作非常快速,通常只需要几个哈希函数的计算和位的检查。
  • 可用于大规模数据:

    • 适用于处理大规模数据集,尤其是在内存有限的情况下
      • 可以快速过滤掉大部分不可能存在的元素,减轻后续查询的压力。

缺陷

误判率:

  • 布隆过滤器可能会产生误判,即判断一个元素存在时,实际上它可能不存在。
    • 这是因为多个元素可能映射到相同的位数组索引,导致冲突。

不支持删除:

  • 由于布隆过滤器的位数组只能设置为1,不能删除元素。
  • 如果需要删除元素,需要重新构建布隆过滤器。

容量不可扩展:

  • 一旦位数组的大小确定,就不能动态扩展
    • 因此需要在设计时估计好位数组的大小以应对数据规模的增长。

RDB 持久化?

RDB(Redis Database)持久化是Redis提供的一种数据持久化方法

  • 它可以将内存中的数据以二进制的形式写入磁盘,以保证数据在重启或者异常情况下的持久性。

RDB持久化工作原理:

快照生成:

  • 当触发RDB持久化时,Redis会将当前内存中的数据通过fork()系统调用创建一个子进程
    • 由子进程负责在后台进行快照生成。

数据存储:

  • 子进程会将内存中的数据按照指定的数据结构和格式保存到一个临时文件中。

替换原文件:

  • 当持久化过程结束后,子进程会将临时文件替换原有的RDB文件。

恢复数据:

  • 在Redis重启的时候,会通过加载RDB文件将数据重新读入内存中。

RDB持久化的优点:

性能高:

  • 由于是在后台进行持久化操作,不会阻塞主线程,所以对Redis的性能影响较小。

容灾性强:

  • 通过RDB文件,可以将数据备份到磁盘中,保证数据在异常情况下的可恢复性。

RDB持久化的缺点:

数据丢失:

  • 由于RDB持久化是通过生成快照的方式进行的
    • 如果Redis在最后一次持久化之后发生故障,会导致最后一次持久化之后的数据丢失。

时效性:

  • RDB持久化是定期执行的,数据的持久化是在配置的时间间隔之后
    • 因此在发生故障之前的数据可能会丢失。

AOF 持久化?

AOF(Append Only File)持久化是Redis提供的另一种数据持久化方法。

通过AOF持久化,Redis将写操作追加到一个文件中

  • 以保证数据在重启或者异常情况下的持久性。

AOF持久化工作原理:

追加写操作:

  • 当有写操作(增删改)发生时,Redis会将这些写操作以文本的形式追加到AOF文件末尾。

文件同步:

  • Redis会通过fsync()系统调用将AOF文件的内容强制刷写到磁盘上,以保证数据的持久性。
  • 可以通过配置appendfsync参数来调整同步频率
    • 可以选择每次写入都同步(always)、每秒同步一次(everysec)或者操作系统自行决定(no)。

文件重写:

  • 当AOF文件变得过大时,可以通过BGREWRITEAOF命令触发AOF文件的重写。
  • Redis会启动一个子进程,将当前内存中的数据重写到一个新的AOF文件中,并且优化写入操作,减小AOF文件的体积。

恢复数据:

  • 在Redis重启的时候,会通过加载AOF文件中保存的写操作来恢复数据,重建内存中的数据状态。

AOF持久化的优点:

数据可靠性高:

  • AOF持久化记录了写操作的历史记录,因此在异常断电或者重启时
    • 可以通过AOF文件将数据快速恢复,避免了数据丢失的风险。

数据实时性高:

  • 相比于RDB持久化的定期快照记录,AOF持久化会实时追加写操作到AOF文件中
    • 因此对于实时性要求较高的应用场景更为适合。

可读性好:

  • AOF文件以纯文本形式记录写操作,可以直接查看和修改AOF文件,方便进行恢复和数据分析。

AOF持久化的缺点:

文件体积较大:

  • 由于AOF记录了所有的写操作历史,因此AOF文件会比RDB文件大,可能会占据更多的磁盘空间。

写入操作耗时:

  • 由于每次写操作都需要追加到AOF文件中,相比于RDB持久化,AOF持久化会有一定的写入延迟,可能会影响Redis的性能。

文件重写需要时间:

  • 当AOF文件变得过大时,进行AOF文件的重写是一项耗时的操作,可能会对Redis的性能产生一定影响。

Redis默认采用哪个持久化方式?

Redis默认采用的持久化方式是RDB(Redis Database)持久化。

  • 在默认配置下,Redis将周期性地将内存中的数据生成快照并写入磁盘,以保证数据的持久性。

RDB持久化通过将数据以二进制的形式保存到磁盘的RDB文件中,包含了Redis数据的全量快照。

  • 可以通过配置文件中的save参数来设置快照生成的条件
    • 比如在指定的时间间隔内、指定的写操作次数等。

Redis 内存淘汰策略有哪些?

Redis提供了一些策略,以便在届满最大内存限制时进行内存淘汰:

noeviction:

  • 当内存不足以容纳更多数据时,新的写入操作会报错。这是默认策略。

allkeys-lru:

  • 在内存不足时让位于新值内容的,是最近最少使用的键(LRU:Least Recently Used)。

volatile-lru:

  • 在设置了过期时间的键中,淘汰最近最少使用的键,新的写入操作会报错。

allkeys-random:

  • 在内存不足时随机删除某个键的值,为新值让出空间。

volatile-random:

  • 在设置了过期时间的键中,随机淘汰一些键。

volatile-ttl:

  • 在设置了过期时间的键中,有更早过期时间的键优先被淘汰。

如果你的程序可以接受偶发的性能下降,allkeys-lru可能是一个好选择。

如果你知道一些键是可以安全删除的,你可以为它们设置过期时间,然后使用volatile-lru

如果数据的重要性不等,你可以为重要的数据设置过期时间,然后使用volatile-ttl策略。

Redis过期键的删除策略

Redis使用过期键的删除策略来自动清除已经过期的键,以释放内存空间。

Redis采用了多种策略来删除过期键,具体的删除策略由配置参数eviction决定。

常见的策略包括:

定期删除策略(定时删除):

  • Redis会在每个指定的时间间隔(由配置参数hz决定)内,检查一批键是否过期,然后删除过期的键。
  • 这种策略不会频繁地检查每个键是否过期,因此对CPU的消耗较少。

惰性删除策略(懒汉式删除):

  • 当访问某个键时,Redis会先检查该键是否过期,如果过期则立即删除。
  • 这种策略相对更加高效,因为它只会在需要时才进行检查和删除操作。

定期删除与惰性删除的结合:

  • Redis同时使用了定期删除和惰性删除两种策略,在有限的时间间隔内通过定期删除来批量清除过期键
    • 同时在读写操作中使用惰性删除来保证及时的清理。

需要注意的是,无论采用哪种删除策略,Redis并不是立即清除过期键

  • 而是通过在查询和写入操作时进行过期键的检查和删除。

因此,在过期时间到达之后,过期键可能仍然存在一段时间,直到Redis执行删除操作。

  • 如果需要确保即时删除过期键,可以使用DEL命令主动删除过期键。

同时,可以通过配置参数maxmemory来限制Redis使用的内存大小

  • 当达到内存限制时,Redis会根据所采用的删除策略来淘汰一些数据以释放内存空间。

Redis 哈希表扩容介绍一下?

Redis的哈希表扩容是指在哈希表需要增加更多的槽位(bucket)来存储元素时进行的一种扩展机制。

当哈希表中的元素数量增加到一定阈值时,Redis会自动触发哈希表的扩容操作。

哈希表扩容的过程如下:

新建更大的空白哈希表:

  • Redis会创建一个更大的空白哈希表,其槽数量通常是当前哈希表槽数量的两倍。

搬移数据:

  • Redis逐个遍历原哈希表中的每个槽位,将非空的槽位中的元素重新计算哈希值,然后放入新的哈希表的对应槽位中。
    • 这个过程称为rehash

渐进式地迁移数据:

  • 为了避免一次性大量数据的搬移导致系统的延迟,Redis采用渐进式的方式进行数据迁移。
    • 每次执行rehash操作时,Redis只处理一小部分槽位,并渐进地将数据从原哈希表迁移到新哈希表中。

更新指针和释放内存:

  • 当新哈希表中的rehash操作完成后,Redis会将指向原哈希表的指针更新为新哈希表
    • 并释放原哈希表所占用的内存。

需要注意的是,在哈希表进行扩容期间,Redis会同时维护原哈希表和新哈希表,保证数据的正常访问。

在数据迁移过程中,读取操作会同时访问两个哈希表,写入操作会先写入新哈希表

  • 并同时更新两个哈希表,以确保数据的一致性。

哈希表扩容是Redis动态调整内存空间的关键机制之一,它通过增加槽位来适应数据的增长

  • 保证了哈希表的性能和容量。

同时,由于采用了渐进式的迁移策略,在数据量较大情况下也能较好地控制系统的稳定性和延迟

Hash 冲突怎么办?

Redis 通过链式哈希解决冲突:

  • 也就是同一个 桶里面的元素使用链表保存。

但是当链表过长就会导致查找性能变差可能,所以 Redis 为了追求快

  • 使用了两个全局哈希表。用于 rehash 操作,增加现有的哈希桶数量,减少哈希冲突。

开始默认使用 「hash 表 1 」保存键值对数据,「hash 表 2」 此刻没有分配空间。

当数据越来越多触发 rehash 操作,则执行以下操作:

  • 给 「hash 表 2 」分配更大的空间;

  • 将 「hash 表 1 」的数据重新映射拷贝到 「hash 表 2」 中;

  • 释放 「hash 表 1」 的空间。

值得注意的是,将 hash 表 1 的数据重新映射到 hash 表 2 的过程中并不是一次性的

  • 这样会造成 Redis 阻塞,无法提供服务。

而是采用了渐进式 rehash,每次处理客户端请求的时候,先从「 hash 表 1」 中第一个索引开始

将这个位置的 所有数据拷贝到 「hash 表 2」 中,就这样将 rehash 分散到多次请求过程中,避免耗时阻塞。

什么是Redis哨兵?

Redis哨兵(Sentinel)是Redis官方推荐的高可用解决方案。

在Redis的主从复制模式中,我们有一个主Redis服务,和多个从Redis服务。

但是,如果此时主Redis服务挂掉了,那么系统就会自动通过Sentinel选举出一个新的主Redis服务

  • 其他的从Redis服务会自动连接新的主Redis服务。这就保证了系统的高可用。

以下是Redis哨充的主要功能:

监控工作

  • 哨兵通过发送命令,来监控主服务器和从服务器是否正常运行。

提供通知

  • 当被监控的某个Redis实例有故障时,Sentinel可以通过API向管理员或者其他应用程序发送通知。

自动故障迁移

  • 当一个主节点不能正常工作时,Sentinel会开始一次故障迁移操作
    • 它会选举出一个从节点来作为新的主节点,并让其他从节点复制新的主节点。

配置提供者

  • 客户端在连接Redis集群时,可以先连接到Sentinel查询主节点和从节点的信息,然后再决定连接到哪个节点。

比如,一个用Redis作为缓存的Web应用,如果使用了Sentinel,那么当Redis主节点由于某些原因宕机后

  • 可以自动进行故障切换,选择一个从节点提升为主节点,以此来保证服务的持续可用
  • 防止由于Redis节点的宕机导致应用无法访问缓存而出现的性能瓶颈。

Redis集群介绍一下

Redis 集群的主要目标是提供一种方式,在逻辑上管理多个 Redis 节点

  • 使它们看起来像一个更大的、逻辑上单一的 Redis 数据库。

  • 这可以提供更多的用例,因为使用 Redis 集群,我们可以处理比单个 Redis 实例更大的内存容量

    • 以及得到更高的每秒请求率(通过在多个节点上并行执行命令获得)。

Redis 集群采用分片技术实现数据的分布式存储。

  • 集群中的每个节点都保存了分片数据,每个元素被分配一个固定的哈希槽,且集群中共有 16384 个哈希槽。

此外,Redis集群也提供了高可用和故障转移的能力。

  • 这是通过对集群中每个主节点配置复制节点实现的。

  • 当集群中的某个主节点出现问题时,集群会自动从该节点的复制节点中选举一个来替代下线的主节点

    • 以保证数据的可用性。

例如,你正在为一个需要大量写入操作和高速查询的社交网络应用设计后端

  • 单个 Redis 实例可能无法处理所需的负载。

  • 你可以使用 Redis 集群将负载分散到多个 Redis 实例上

    • 同时通过自动分片和故障转移提供对大数据集的高效访问和稳定性。

Redis分区有什么缺点?

Redis分区是将数据拆分到多个Redis实例的策略,以达到可扩展性的目的。

  • 然而,它并非没有缺点。以下是一些主要的缺点:

复杂性:

  • Redis 分区需要在客户端或代理层解决,这会导致客户端实现变得更复杂,需要处理在不同实例之间切换。
    • 此外,如果选择了错误的分区策略,可能需要重新分区,这也是非常复杂的。

操作限制:

  • 不支持多个key的操作,例如,你无法执行涉及多个key的命令如MGET,MSET和事务等。

数据冗余:

  • Redis分区可能导致大量冗余连接。

分区选择困难:

  • 选择一个好的分区策略是非常困难的,要根据你的应用需求进行权衡。

不支持数据之间的强一致性:

  • 如果你的应用需要强一致性,那么你需要在应用层实现,以保证不同分区之间的数据一致性。

实际上,选择是否使用分区应该视具体应用的需求而定。

如果你的Redis数据可以很自然地被分割,并且对于数据冗余,一致性等问题可以接受

  • 那么分区是一个很好的方式来扩展你的Redis应用。

谈一谈你对Redis事务的理解?

Redis事务是一种将多个命令请求打包,一次性、按顺序地执行所有命令的机制,主要包括以下几个命令:

  • MULTI:标记一个事务块的开始。

  • EXEC:执行所有事务块内的命令。

  • DISCARD:取消事务,放弃执行事务块内的所有命令。

  • WATCH:监视键,如果键的值在事务执行之前发生改变,事务队列中的命令就不会执行。

值得注意的是,Redis的事务与传统意义上的数据库事务略有不同。

在传统数据库中,事务有所谓的ACID属性——原子性、一致性、隔离性、持久性。

但在Redis中,只支持命令的原子性

  • 即事务队列中的命令会作为一个原子连续、中断地执行,执行过程中不会被其他命令插入。
  • 但是,如果事务队列中的某个命令执行失败,Redis并不会回滚其他已经执行的命令。

Redis事务是否支持回滚?

在 Redis 中,事务是不支持回滚的。

  • 一旦调用 EXEC 命令执行事务,其中的所有命令都会被按顺序执行,并且不会发生回滚。
    • 即使在事务中某个命令执行失败,也不会影响其他命令的执行。

在 Redis 中,事务的执行过程类似于原子性的批处理,其中的每个命令都会按照顺序执行。

  • 如果其中的某个命令执行失败,Redis 会继续执行剩余的命令,并将失败的命令的错误信息返回给客户端。

由于 Redis 是单线程的,它不支持在事务中进行回滚操作,即无法撤销已经执行的命令。

所以,使用 Redis 的事务时,我们需要在客户端代码中进行错误处理和逻辑判断

  • 以确保事务中的操作具有一致性。

  • 如果需要支持回滚或更复杂的 ACID 特性,那么应该考虑使用其他支持事务回滚的数据库系统。

Redis为啥要选择单线程?

Redis选择单线程模型主要是基于以下考虑:

简单性和清晰性

  • 单线程模型会使Redis的设计和实现变得简单、清晰。无需担心各种复杂的同步、数据一致性和线程通信问题。
    • 这也大大提高了Redis的稳定性。

避免上下文切换开销:

  • 多线程和多进程程序需要频繁地进行系统调用,例如创建线程、进程间通信、上下文切换等。
    • 这些操作相对于Redis的内存操作来说,CPU时间开销是巨大的。

CPU瓶颈不是Redis的性能瓶颈:

  • Redis是基于内存的数据库,所以主要的性能瓶颈在于网络I/O和磁盘I/O,而不在CPU。
    • 通过使用单线程模型,Redis可以充分利用单核CPU的性能,而避免了线程切换和锁竞争的开销。

高效的事件驱动模型:

  • Redis使用高效的事件驱动模型,即使是单线程也能处理高并发的网络连接和请求。

到目前为止,虽然Redis是单线程模型,但是其性能一直很高,可以支持每秒处理上百万次的读或者写操作。

当然,从某种角度来说,Redis并非完全的单线程。

  • 比如在进行RDB、AOF持久化以及主从复制的时候,Redis就会创建新的子进程。

  • 但这种方式不会引发复杂的数据的同步和一致性问题。

介绍一下Redis的主从架构模式

Redis主从复制模式是Redis Server之间的数据同步技术。

在主从模式中,数据的写入操作只在主节点进行,而从节点负责读操作。

  • 当主节点数据发生改变时,这种改变也会自动同步到从节点。

以下是Redis主从架构模式的基本过程:

  • Slave启动成功连接到master后发送SYNC命令;

  • Master接到SYNC命令开始执行BGSAVE命令生成RDB文件

    • 并使用一个缓冲区记录此后执行的所有写命令;
  • Master执行完BGSAVE命令后,将RDB文件数据发送给Slave

    • Slave接收到这份数据后载入并开始接收Master缓冲区中的写命令;
  • Master每执行完一个写命令就自动将写命令发送给Slave,Slave接到写命令后也会执行。

下面是主从复制的优点和应用场景:

优点:

数据冗余:

  • 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

副本扩展:

  • 在主从复制的基础上,配合读写分离,可以有效降低单个服务器的负载。

高可用:

  • Master节点挂掉之后,可以由Slave节点提供服务。

应用场景:

  • 读写分离,降低单个服务器的负载,可以扩展出非常强大的性能,负载均衡等应用。

不过,需要注意的是,Redis的主从复制模式虽然具有很多优点,但也存在一些问题

  • 如数据一致性问题,主进程因某些原因停止,备份进程不能及时知道,有可能出现数据丢失等问题。
    • 所以在使用时需要根据具体情况进行权衡。

在Redis中,如果Key太大了,容易出现什么问题?

在Redis中,如果key的值过大,可能会出现以下几个问题:

内存问题:

  • Redis全量数据存储在内存中,如果key值过大,过多,会消耗大量内存资源
    • 可能导致Redis实例内存溢出,甚至导致实例崩溃。

网络问题:

  • Redis是基于Client-Server模型的网络应用,数据需要在网络中进行传输。
    • 如果数据项过大,那么网络传输会占用更多时间,对于对时效性有要求的业务情况下会造成延迟,影响用户体验。

CPU问题:

  • 当key很大时,Redis在处理HASH这类数据结构的时候,计算hash值,寻找key所在位置等都会消耗更多CPU资源。

为了避免这些问题,一般建议对Redis的key

  • 以及对应的value做适当的设计和限制,使其既能满足业务需要,又不会导致资源过度消耗。

例如,对于一些大文本或者图片等资源,可以选择在其他地方存储(如数据库或者文件系统)

  • 然后将其ID或者访问路径作为其对应的value存储在Redis中。

什么是缓存预热?

缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。

  • 避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量

  • 都会访问到数据库中, 对数据库造成流量的压力。

缓存预热解决方案:

  • 数据量不大的时候,工程启动的时候进行加载缓存动作
  • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新
  • 数据量太大的时候,优先保证热点数据进行提前加载到缓存

Redis 6.0为何引入多线程?

虽然Redis的主要操作仍然是单线程执行的,但是在Redis 6.0版本中

  • 它引入了多线程来处理某些特定的任务,特别是网络I/O的处理。
  • 这是因为随着硬件和网络技术的发展,多核处理器和高速网络已经变得普遍
    • 对于CPU密集型的服务来说,单线程可能无法充分利用这些资源。

具体来说,在Redis 6.0中,主线程负责执行命令

  • 而额外的IO线程则用来处理客户端和服务器之间的数据交换,包括接收请求和发送响应。

这样做的好处是可以充分利用多核处理器,同时还能减少因网络I/O阻塞导致的处理速度下降。

需要注意的是,这些I/O线程并不会直接处理Redis命令,执行命令仍然是单线程的。

只是在读取客户端请求和发送响应的时候使用了多线程处理。

  • 这样在提升吞吐量的同时,避免了多线程编程中数据一致性和同步问题的复杂性。

此外,Redis 6.0版中的多线程默认是关闭的,需要在Redis的配置文件中手动开启。

  • 使用io-threads选项可以指定线程数,使用io-threads-do-reads选项可以启动多线程。

讲一下哨兵选举主节点的策略?

Redis Sentinel 在主节点故障时会选择一个从节点晋升为新的主节点,过程如下:

故障检测

  • Sentinel会不断地通过心跳检测Redis节点的健康状态。
    • 当主节点不可达时,检测到主节点down掉,这个Sentinel会等待指定时间(例如10秒)后,开始下一步。

发起投票:

  • 该Sentinel会向其他Sentinels发送一个故障转移的请求,请求得到的多数同意(超过半数)就会开始主节点的故障转移。

选择新的主节点:

  • Sentinel会选择一个从节点来进行晋升。
  • 选择策略主要考虑以下因素:
    • 复制偏移量最大的节点(即数据最新的节点)、运行ID较小的节点
      • 没有被其他Sentinel标记为主观下线的节点。
    • 先按照第一规则选举,如果都相等按照第二规则,依此类推,最后能找出一个从节点。

晋升选举出的从节点为主节点

  • Sentinel向选举出的从节点发送命令,关闭其对旧的主节点的同步,让其成为新的主节点。

通知其他从节点、客户端和Sentinels更改主节点

  • 晋升成功后,该Sentinel会通知其他的从节点、客户端和Sentinel更改主节点。

注意:整个故障转移过程中,可能会有多个Sentinel发起投票,但只有先获得大多数Sentinel认可的才能开始故障转移

  • 且在故障转移过程中,其他Sentinel不会再发起新的投票。

Redis 6.0 多线程的实现机制?

在Redis 6.0版本中,被引入的多线程并不用于处理核心的数据读写等操作,而是用来进行网络IO的处理。

  • 具体来说,副线程主要用于从客户端套接字中读取请求数据和向客户端套接字中写入响应数据的过程。

多线程的具体实现逻辑如下:

  • 当主线程准备读取请求时,会判断开启的IO线程数量是否大于0

    • 如果大于0,则主线程将套接字分配给IO线程,并在分配完之后阻塞等待IO线程读取完请求数据。
  • IO线程在读取完请求数据之后,会将完整的请求数据放入一个由主线程进行消费的队列中。

  • 主线程在所有的IO线程将请求数据读取完之后会解除阻塞

    • 接着主线程会从队列中取出请求数据进行处理,并生成响应数据。
  • S当主线程准备写入响应数据到套接字时,会首先判断开启的IO线程数量是否大于0,如果大于0

    • 则主线程将响应数据以及对应的套接字等信息放入待写队列,并唤醒IO线程去处理待写队列中的任务。
  • IO线程在取出待写队列中的响应数据后,会将响应数据写入到对应的套接字中,并在写入完成后通知主线程。

  • 主线程在所有的IO线程写入完数据后,会进行下一轮的处理,即返回到步骤1。

通过以上机制,Redis6.0可以充分利用多核优势,提高网络IO的处理能力,同时它也保证了数据的处理仍是单线程的

  • 避免了多线程处理数据时可能产生的数据不一致等问题。
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!