Redis:分享Redis高频面试题

前言

本篇博客主要分享Redis高频面试题,通过博客知识点汇总,梳理以往掌握的技术

谈谈Redis的持久化,RDB快照与AOF日志

Redis支持两种持久化方式:RDB快照和AOF日志。
RDB快照:

  • RDB是Redis数据库的定期快照,RDB会定期将内存中数据集写入磁盘,实现数据的持久化。
  • RDB的优点是保存和恢复都很快,缺点是会丢失最后一次快照之后修改的数据。
  • 典型的RDB保存策略是:60s内有至少1个键值发生变化,则进行快照。
    AOF日志:
  • AOF会记录服务器执行的所有写入命令,并在服务器启动时,通过重新执行这些命令来还原数据集。
  • AOF的优点是数据非常完整,缺点是相比RDB占用更多磁盘空间,恢复速度更慢。
  • AOF存在两种模式:APPEND和 REWRITE。APPEND模式下,AOF文件持续增长;REWRITE模式下,Redis会定期对AOF文件重写,使得AOF文件的体积不会过大。
    两种持久化方式的选择:
  • 如果想要数据的绝对完整性,选择AOF。如果没有这么强的要求,选择RDB。
  • 也可以同时开启两种持久化方式,在这种情况下,服务器重启后会优先加载AOF文件来恢复数据集。RDB的数据集只在AOF文件被删除或破损时使用。
  • 一般来说,RDB用于备份,AOF用于灾难恢复。RDB能保证较快的恢复速度,AOF能提供较高的数据完整性。

Redis主从复制与Redis哨兵机制

  • Redis主从复制用于实现Redis高可用,通过将主服务器的数据同步到从服务器上实现对数据的热备份。
  • 主从复制的工作过程:主服务器将写命令复制给从服务器,从服务器将主服务器的数据同步过来,实现数据的热备份。
  • 主从复制的配置步骤:
    1. 配置主服务器,设置bind、port等参数,打开masterauth和requirepass参数来设置密码。
    2. 配置从服务器,设置slaveof 来指定主服务器,并设置masterauth 来输入主服务器密码。
    3. 从服务器连接主服务器后会自动定期同步数据。
      Redis哨兵机制:
  • Redis哨兵用于管理多个Redis主从结点,监控主结点是否下线,如果主结点下线会自动将从结点提升为主结点。
  • Redis哨兵的工作过程:
    1. 监测主结点是否正常工作,如果主结点故障,则根据投票数自动将从结点提升为主结点。
    2. 监控主结点和从结点的信息,如主从结点的IP,端口,运行状况等信息。
    3. 提供主从结点的管理操作接口,如 failover、promote 主结点等。
  • Redis哨兵的配置步骤:
    1. 配置哨兵配置文件sentinel.conf,定义监控的主从结点。
    2. 启动Redis哨兵,它会自动发现主从结点并监控。
    3. 当主从结点发生故障时,哨兵会自动进行failover操作,将从结点提升为主结点。
    4. 客户端应用需要知道新的主结点信息,因此客户端需要订阅Redis哨兵的通知来获得新的主结点信息。

      Redis单线程还是多线程?IO多路复用原理

      Redis是单线程的:

      • Redis选择单线程模型主要是为了避免线程上下文切换带来的开销。Redis大量使用内存操作,如果引入线程会增加很大的复杂度,所以Redis选择单线程模型。
      • Redis单线程模型如何实现高并发?主要是利用IO多路复用机制。IO多路复用可以监听多个socket,并在socket有事件发生时(比如recv/send就绪)通知程序进行相应的读写操作。这样一个线程就可以管理多个客户端连接和请求。
        IO多路复用的原理:
      • IO多路复用系统调用(如select、poll、epoll等)可以监视多个文件描述符,一旦某个文件描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
      • 通过这样的机制,一个线程可以管理多个网络连接的读写操作,这就是Redis高并发的基础。
      • Redis使用的IO多路复用模型是epoll:
    5. epoll基于事件驱动,只有在事件发生时才会真正调用回调函数。这避免了select和poll经常无意义的轮询。
    6. epoll使用fd维护每个事件,当新事件产生时添加新的fd,这样只需要在fd上操作,避免每次都要遍历整个数组。
    7. epoll支持一个线程管理成百上千个描述符,这正是Redis高并发的基础。
    8. epoll在修改fd关注的事件上更加高效。只需要修改内核事件表中的对应fd的事件就行,不必像select和poll那样重置整个待监听fd集合。
      所以,总结一下,Redis选择单线程模型,通过IO多路复用(epoll)实现高并发和高性能。这种模型简单高效,很适合Redis这种内存数据库。

      Redis bigkeys命令会阻塞吗?怎么解决?

      Redis的bigkeys命令是用来扫描整个数据库并返回键值对大小超过一定阈值的键,这个命令可能会阻塞主线程。
      bigkeys命令可能会阻塞的原因:

    9. bigkeys命令需要扫描整个数据库,而Redis是单线程的,所以这个过程会占用主线程。
    10. 如果遇到大键值对,需要花费较长时间进行序列化和计算键值对大小,这会 further 阻塞主线程。
    11. 如果bigkeys正在执行,很可能其他客户端命令会阻塞,影响正常操作。
      解决bigkeys命令阻塞的方法:
    12. 避免在高峰期执行bigkeys命令。可以在低峰期定期执行bigkeys命令,监控数据库的键值对分布情况。
    13. 设置部分键过期时间较短,让其自动过期。过期的键值对不会被bigkeys命令计算在内,这样可以减少bigkeys占用的时间。
    14. 使用Redis的发布订阅功能,将bigkeys命令的执行结果通过消息通道发布出去,其他客户端可以订阅该消息频道获得bigkeys输出的结果,避免直接执行bigkeys导致阻塞。
    15. 使用Redis的client filter功能,仅在特定客户端上执行bigkeys命令。其他普通客户端的操作不会被阻塞。
    16. 在Redis配置中设置client-output-buffer-limit pubsub等参数,避免bigkeys输出结果过大导致其他客户端命令被阻塞。
    17. 如果bigkeys实在影响性能,可以关闭该命令。使用其他方式定期监控和清理数据库大键。
      综上,避免高峰期执行bigkeys,使用发布订阅和client filter等功能可以有效减少bigkeys对其他客户端的影响,防止bigkeys阻塞主线程。同时应定期清理数据库中过期或占用空间较大的键,这也有助于bigkeys性能的提高。

NoSQL基础

NoSQL数据库进阶实战,需要具备以下NoSQL基础知识:

  1. NoSQL数据库的四大分类:键值对存储数据库(如Redis)、列存储数据库(如HBase)、文档存储数据库(如MongoDB)、图形数据库(如Neo4j)。不同类型的NoSQL数据库应用于不同的场景。
  2. CAP理论:一个分布式系统不可能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)三个要求。不同的NoSQL数据库在这三者之间作出不同的权衡。
  3. NoSQL数据库的扩展方式:主要有主从复制、分片、联合等方式实现扩展。不同的NoSQL数据库采用不同的方式,比如Redis使用主从复制,MongoDB使用分片。
  4. NoSQL数据库的高可用方案:一般通过主从复制、哨兵、集群等机制来实现高可用。例如Redis使用哨兵,MongoDB使用副本集。
  5. NoSQL数据库的一致性方案:NoSQL数据库一般会采用最终一致性,通过主节点同步数据到从节点,从而在一定时间内达到数据一致。一致性级别会影响系统的吞吐量和可用性。
  6. 各类型NoSQL数据库的特性和常用数据模型:
    • 键值存储:FAST, STRING, HASH, LIST, SET, ZSET 等数据类型。典型数据库Redis。
    • 列存储:不同的列簇存储不同列的数据。典型数据库HBase、Cassandra。
    • 文档存储:文档以JSON或BSON格式存储,嵌套性强。典型数据库MongoDB、CouchDB。
    • 图形数据库:节点和关系构成图结构。典型数据库Neo4j、JanusGraph。
      熟练掌握NoSQL的这些基础知识,可以更好地理解和运用NoSQL数据库,进行进阶实战和应用。NoSQL的具体应用也需要结合实际业务场景,选择合适的NoSQL数据库和方案。

      NoSQL数据存储模式

      NoSQL数据库有多种数据存储模式,主要有:

  7. 键值对存储:
    • 键值对存储是NoSQL数据库最简单的存储模式。数据以键值对的形式存储,键作为查询条件,获取对应的值。
    • 典型的键值对数据库有Redis、Voldemort等。Redis支持多种数据类型:STRING、HASH、LIST、SET、ZSET等。
    • 适用场景:缓存,会话管理,查找数据等。
  8. 列存储:
    • 列存储将同一列的数据存储在一起。每个列以列族的形式存储,一个行可以有不同的列族。
    • 典型的列存储数据库有HBase、Cassandra等。HBase基于Hadoop和HDFS,Cassandra基于P2P网络。
    • 适用场景:分布式存储大数据, nosql数据库。
  9. 文档存储:
    • 文档存储将数据以文档形式存储,文档是自包含的,嵌套的键值对,文档存储不关心数据的格式。
    • 典型的文档数据库有MongoDB、CouchDB等。MongoDB面向大数据应用并支持内嵌的文档和数组,CouchDB可用于web应用作为数据库。
    • 适用场景:web应用,移动应用后端,用于处理不规则数据。
  10. 图存储:
    • 图形数据库使用节点、边和属性来表示和存储数据。节点表示实体,边表示节点之间的关系。
    • 典型的图形数据库有Neo4j、JanusGraph等。Neo4j是最流行的图形数据库,JanusGraph是一个 scalable 图形数据库。
    • 适用场景:社交网络,推荐系统,网络安全分析等。
      以上是NoSQL数据库常见的几种数据存储模式,不同的存储模式针对不同的应用场景,选择一个合适的存储模式很重要。可以根据具体的业务需求选择不同的NoSQL数据库。

      Redis缓存穿透、击穿、雪崩到底是个啥

      Redis缓存可能会出现三种问题:穿透、击穿和雪崩。

  11. 缓存穿透:
    发生在缓存总是不命中时,请求数据只在数据库中查询,而缓存根本没有起到作用。这通常是由于缓存的key设计不当造成的。解决办法是使用布隆过滤器检查key是否存在。
  12. 缓存击穿:
    发生在缓存过期导致大量请求落到数据库上,使数据库短时间内承受大量请求。这可能会对数据库造成很大压力。解决办法是在Redis设置过期时间的同时,设置好热点key的过期时间,并在过期后通过加锁或队列的方式控制对数据库的访问速率。
  13. 缓存雪崩:
    发生在缓存服务器宕机或断网后,大量的请求会落到数据库上,给数据库造成很大压力。解决办法是在缓存和数据库之间加入消息队列,然后通过消息队列控制访问数据库的速率。
    总结:

    • 缓存穿透:key未命中,大量请求落到数据库。使用布隆过滤器避免。
    • 缓存击穿:热点key过期,大量请求落到数据库。设置热点key长过期时间,访问数据库使用加锁或队列控制速率。
    • 缓存雪崩:缓存宕机,大量请求落到数据库。数据库和缓存加入消息队列,控制访问数据库速率。
      以上三种情况都可能会导致数据库过载,需要在系统设计时做好容错和限流处理。合理的缓存数据和过期时间也可以降低这三种情况的出现概率。

      Redis分布式锁的实现方式

      Redis分布式锁的实现方式主要有:

  14. 使用SETNX(set if not exists)命令:
    • 如果键NX已经存在,SETNX不做任何操作并返回0。
    • 如果键NX不存在,SETNX会将NX设置为指定值并返回1。
    • 通过这个特性可以实现分布式锁。如果SETNX设置成功,表示获得锁;如果SETNX失败,表示锁已经被其他客户端获取。
      实现代码:

      import redis
      conn = redis.Redis()
      # 获取锁
      result = conn.setnx("lock", "value")
      if result == 1:
      print("Obtain lock success!")
      else:
      print("Lock already exist!")
      # 释放锁  
      conn.delete("lock")
  15. 使用Redis的 RedLock算法:
    RedLock算法可以在分布式系统中实现互斥锁。它的原理是:

    • 客户端获取多个Redis节点的锁
    • 如果客户端从大多数节点获取锁,则获获得锁
    • 如果没有从大多数节点获取锁,释放已获得的锁并重复上述过程
    • 该算法可以减少因网络延迟等问题导致的锁不一致的可能性。
      具体实现可以参考Redisson等开源组件。
  16. 使用 Lua脚本进行锁定:
    可以使用EVAL命令执行Lua脚本进行锁定。如果一个客户端的锁定操作执行成功,其它客户端的锁定操作会失败。
    这个方法的优点是原子操作,可以避免A客户端设置了锁,但B客户端查询时仍未发现的情况。
    Lua脚本示例:

    if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1]) 
    else
    return 0
    end

    Redis分布式缓存、秒杀

    Redis可以用于实现分布式缓存和秒杀系统。

  17. Redis分布式缓存:
    • 分布式缓存是为了解决单机缓存内存限制和单点故障问题。Redis通过主从复制可以实现分布式缓存。
    • 客户端请求首先会在本地缓存查找,如果没有命中则会去Redis缓存集群查找,如果还是没有则会去后端数据库查询。
    • 分布式缓存架构:
      分布式缓存架构
    • 使用分布式缓存可以提高系统吞吐量和减轻数据库压力。
  18. Redis秒杀系统:
    • 秒杀系统需要解决高并发下的商品超卖问题。可以通过redis的唯一性和原子性操作实现商品库存的实时减减和下单操作。
    • 具体流程:
      1) 初始化商品库存数量 stock = N,这个值存储在Redis的String类型键中。
      2) 用户下单时,使用Redis的 DECR(stock) 命令将库存减1。如果减1成功,说明抢购成功;如果失败,说明商品售罄。
      3) DECR命令是Redis的原子操作,可以在高并发情况下实现商品库存的准确减减。
      4) 还需要注意其他要点,如商品超卖校验、订单号生成、支付过程中的库存校验等。
    • 这种方案可以在一定程度上解决高并发环境下的秒杀问题,但是仍存在Redis的性能瓶颈可能导致问题。更高级的方案是采用队列或者消息中间件来控制流量和延迟秒杀开始时间。
      总之,Redis的分布式特性和原子操作可以用于实现分布式缓存和秒杀等高并发场景。但是存在Redis性能和网络带宽限制,实际生产环境可能需要更复杂的解决方案。

      既然有MySQL了,为什么还要有MongoDB?

      MySQL和MongoDB都是常用的数据库,但适用于不同的场景:

  19. MySQL是关系型数据库,MongoDB是非关系型数据库。
    • MySQL存储数据的方式是表形式,表与表通过主键和外键关联。适合存储结构化数据。
    • MongoDB存储数据的方式是文档形式,无表结构,更灵活。适合存储半结构化数据。
  20. MySQL支持ACID事务,MongoDB支持的不是那么强大。
    • MySQL可以保证事务的原子性、一致性、隔离性和持久性。适用于对事务要求严格的应用场景。
    • MongoDB的事务支持不是那么强大,但可以满足大多数应用场景。适用于对事务要求不那么严格的场景。
  21. MySQL有丰富的函数,MongoDB的函数较少。
    • MySQL作为关系型数据库,函数丰富,聚合操作方便。适用于需要复杂函数、聚合的应用场景。
    • MongoDB是非关系型数据库,函数和聚合相对较少。适用于简单查询和存储为主的应用场景。
  22. MySQL查询性能优于MongoDB,MongoDB写入性能优于MySQL。
    • MySQL通过表结构和索引支持复杂查询和优化。读性能较高,适用于查询较频繁的应用。
    • MongoDB的文档存储方式和BSON格式的查询效率稍低。但BSON格式的写入效率高,更新方便。适用于写为主的应用。
      综上,MongoDB和MySQL各有优点,可以根据应用场景进行选择:
    • 需要事务和复杂查询的应用选择MySQL。
    • 需要灵活的数据结构和高写入吞吐的应用选择MongoDB。
    • 也可以MySQL用于核心业务,MongoDB用于日志、缓存等业务,实现互补。
      所以,MongoDB的出现,并不是要替代MySQL,而是针对MySQL不太擅长的某些应用场景,提供更好的解决方案。两者可以很好地配合使用。

      Redis集群的最大槽数为什么是16384个?

      Redis集群的最大节点数是16384个,这个限制源于Redis集群的实现方式。
      Redis集群使用哈希槽(hash slot)来实现对键空间的划分,每个哈希槽对应一个或多个节点。Redis集群一共有16384个哈希槽,所以理论上最大可以有16384个节点。
      为什么选择16384这个数字?这主要出于以下考虑:

  23. 哈希槽数应足够大,以支持比较大的集群,16384应该可以满足大多数需求。
  24. 哈希槽数不应太大,否则会增加节点转发请求的次数,影响效率。16384应在满足需求和效率之间达到一个平衡。
  25. 2^14=16384,使用2的指数次方可以更方便地通过高位运算来实现对节点和哈希槽的映射,这在代码实现上会更高效。
  26. 未来如果16384个槽无法满足需求,Redis还可以实现集群延展,将多个集群连接起来,但每个集群内部仍然是16384个槽。
    除了最大节点数限制外,Redis集群还有其他限制:
  27. 节点间通信使用TCP端口,每个节点占用两个端口,因此最大节点数也受系统最大 TCP 连接数的影响。
  28. Gossip协议需要O(N^2)个TCP连接,太多节点会导致Gossip信息同步效率下降。
  29. 经典主从复制同步也需要O(N)个TCP连接,太多节点会影响主从复制效率。
  30. 少量大键会被映射到同一个槽上,可能导致槽上的负载过高。这个可以通过重哈希等手段解决。
    综上,16384个节点应该可以满足绝大多数用户的需求。但实际部署时,还是需要考虑各种限制,根据心的实际业务情况选择一个适当的集群大小,不必一定达到最大限制。保证集群稳定和高效运转更为重要。
Redis:分享Redis高频面试题

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

滚动到顶部