【万字面试题】Redis

常见面试题

  1. 什么是Redis?

    • Redis是一个开源的内存中数据结构存储,它支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,并提供了持久化、复制、高可用等功能。
  2. Redis的特点是什么?

    • 内存中存储:数据存储在内存中,因此读写速度非常快。
    • 数据结构丰富:支持多种数据结构。
    • 持久化:支持RDB快照和AOF日志两种持久化方式。
    • 高可用:通过主从复制和哨兵或集群实现高可用性。
    • 分布式:支持分片来扩展数据集大小。
  3. Redis支持哪些数据结构?

    • 字符串(String)
    • 哈希(Hash)
    • 列表(List)
    • 集合(Set)
    • 有序集合(Sorted Set)
  4. 什么是Redis的持久化?

    • 持久化是指将数据保存到磁盘上,以便在Redis重启时恢复数据。Redis提供了两种持久化方式:RDB(Redis Database)快照和AOF(Append Only File)日志。
  5. Redis的主从复制是什么?

    • 主从复制是指将一个Redis服务器的数据复制到多个其他Redis服务器,其中一个为主服务器(master),其余为从服务器(slave)。从服务器会持续从主服务器同步数据,以实现数据备份和读取负载均衡。
  6. Redis的集群是什么?

    • Redis集群是多个Redis实例的组合,通过分片的方式存储数据,以提高数据集的大小和吞吐量。
  7. 如何保证Redis的高可用性?

    • 使用主从复制和哨兵机制来监控主服务器的状态并在主服务器宕机时自动切换到备用主服务器。
    • 使用Redis集群来实现数据分片和自动故障转移。
  8. Redis和Memcached有什么区别?

    • Redis支持更多的数据结构(如哈希、列表、集合、有序集合等)而Memcached只支持简单的键值对。
    • Redis支持持久化,而Memcached不支持。
    • Redis支持主从复制和集群,而Memcached不支持。
  9. Redis的数据淘汰策略有哪些?

    • LRU(Least Recently Used):删除最近最少使用的数据。
    • LFU(Least Frequently Used):删除最不经常使用的数据。
    • TTL(Time To Live):设置键的生存时间,过期后自动删除。
  10. 如何使用Redis实现分布式锁?

    • 使用SETNX(SET if Not eXists)命令来设置锁,如果键不存在则设置成功,否则失败。
    • 使用EXPIRE命令为锁设置过期时间,防止锁被长时间占用。
    • 释放锁时使用DEL命令删除键。
  11. Redis的事务支持是怎样的?

    • Redis事务通过MULTI、EXEC、DISCARD和WATCH命令实现,它可以确保一系列命令的原子性执行。
  12. Redis的发布订阅功能是如何工作的?

    • Redis的发布订阅功能允许客户端订阅频道,当有消息发布到频道时,订阅者会收到消息。
  13. 如何使用Lua脚本在Redis中实现原子性操作?

    • Redis通过EVAL命令执行Lua脚本,可以在脚本中实现复杂的原子性操作。
  14. Redis支持哪些客户端?

    • Redis支持多种客户端,包括官方提供的Redis-cli、Jedis(Java)、redis-py(Python)、hiredis(C)等。
  15. Redis的持久化机制对性能有什么影响?

    • RDB持久化机制会在指定的时间间隔生成快照文件,可能会影响Redis的性能。
    • AOF持久化机制会在每个写操作后追加到文件中,可能会影响写入性能。
  16. 如何优化Redis的性能?

    • 使用合适的数据结构。
    • 合理设置缓存失效时间。
    • 使用批量操作。
    • 使用Pipeline减少网络开销。
    • 使用连接池减少连接开销。
  17. Redis集群的工作原理是怎样的?

    • Redis集群通过分片的方式将数据分布到多个节点上,并通过Gossip协议进行节点间通信和数据同步。
  18. Redis的事件驱动模型是怎样的?

    • Redis使用epoll或kqueue等事件驱动模型来处理网络事件,以提高性能和吞吐量。
  19. Redis如何处理并发访问?

    • Redis是单线程的,但通过事件驱动模型和非阻塞IO来处理并发访问,保证了高并发的性能。
  20. 如何在Redis中实现分布式锁的可重入性?

    • 可以使用Redlock算法来实现分布式锁的可重入性,它通过在锁的value中存储锁的持有者和持有次数来实现。
  21. 如何防止Redis的缓存击穿?

    • 可以使用布隆过滤器来过滤不存在的键。
    • 可以设置热门数据的永久有效期,避免热数据过期后频繁重建。
  22. Redis的AOF持久化和RDB持久化的优缺点是什么?

    • AOF持久化提供了更好的数据安全性,但文件体积较大,恢复速度较慢。
    • RDB持久化文件体积较小,恢复速度较快,但可能会丢失最后一次持久化后的数据。
  23. Redis的集群模式下如何进行故障转移?

    • 当主节点故障时,Redis集群会选举一个新的主节点来接替,然后从已有的从节点中选出一个节点作为新的主节点。
  24. Redis的缓存穿透问题如何解决?

    • 可以使用布隆过滤器来过滤无效的请求。
    • 可以设置空值缓存,避免频繁查询不存在的键。
  25. Redis的内存淘汰策略有哪些?

    • LFU(Least Frequently Used):删除最不经常使用的数据。
    • LRU(Least Recently Used):删除最近最少使用的数据。
    • Random(随机淘汰):随机删除数据。
  26. Pipeline 有什么好处,为什么要用 Pipeline?
    可以将多次 IO 往返的时间缩减为一次,前提是 Pipeline 执行的指令之间没有因果相关性。使用 redis-benchmark 进行压测的时候可以发现影响 Redis 的 QPS 峰值的一个重要因素是 Pipeline 批次指令的数目。

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

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

相关知识:Redis 提供 6 种数据淘汰策略:

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

布隆过滤器

布隆过滤器(Bloom Filter)是一种用于快速判断一个元素是否可能存在于一个集合中的数据结构。它的核心思想是通过多个哈希函数将元素映射到一个位数组中,并在查询时检查相应位是否被置位。

原理和数据结构:

  1. 位数组(Bit Array): 布隆过滤器使用一个二进制位数组来表示集合,所有位初始化为0。
  2. 哈希函数(Hash Functions): 布隆过滤器需要多个哈希函数,每个哈希函数可以将输入映射到位数组的不同位置。
  3. 插入操作: 当向布隆过滤器中插入一个元素时,将该元素通过多个哈希函数映射到位数组的相应位置,并将这些位置的位设置为1。
  4. 查询操作: 当查询一个元素是否存在于布隆过滤器中时,将该元素通过相同的哈希函数映射到位数组的相应位置,如果所有位置的位都为1,则可能存在于集合中;如果有任何一个位置的位为0,则该元素一定不存在于集合中。

特点和应用场景:

  1. 空间效率高: 布隆过滤器只需要存储少量的位信息,空间利用率很高。
  2. 查询效率高: 查询一个元素的时间复杂度是常数级别的,与集合大小无关。
  3. 存在误判: 布隆过滤器可能会把不属于集合的元素误判为属于集合(false positive),但不会漏掉实际属于集合的元素。
  4. 应用场景: 布隆过滤器常用于缓存、网络爬虫的URL去重、拦截垃圾邮件等场景,可以有效减少不必要的查询或操作。

缺点和注意事项:

  1. 存在误判: 布隆过滤器可能会误判元素属于集合,因此在一些对准确性要求较高的场景中需要谨慎使用。
  2. 不支持删除操作: 由于删除元素会影响到其他元素的判断结果,布隆过滤器一般不支持删除操作。
  3. 哈希函数选择: 哈希函数的选择对布隆过滤器的性能和误判率有很大影响,需要根据实际情况选择合适的哈希函数。

布隆过滤器是一种简单而有效的数据结构,能够在空间和时间上提供很好的性能,但需要根据具体场景合理使用,并注意其可能存在的误判问题。

在python中使用布隆过滤器

在Python中,你可以使用第三方库pybloom_live来实现布隆过滤器。下面是一个简单的示例,演示如何在Python 3中使用布隆过滤器:

首先,你需要安装pybloom_live库。你可以通过以下命令使用pip进行安装:

pip install pybloom-live

接下来,你可以使用以下示例代码来创建并使用布隆过滤器:

from pybloom_live import BloomFilter

# 创建一个布隆过滤器,设置预期容量为10000,期望误差率为0.001
bf = BloomFilter(capacity=10000, error_rate=0.001)

# 添加元素到布隆过滤器
bf.add("apple")
bf.add("banana")
bf.add("orange")

# 检查元素是否存在于布隆过滤器中
print("apple" in bf)  # 输出 True
print("grape" in bf)  # 输出 False,可能是误判

# 布隆过滤器不支持删除操作

# 保存布隆过滤器到文件
with open("bloom_filter.bf", "wb") as f:
    bf.tofile(f)

# 从文件中加载布隆过滤器
with open("bloom_filter.bf", "rb") as f:
    bf_loaded = BloomFilter.fromfile(f)

# 检查加载后的布隆过滤器是否包含元素
print("apple" in bf_loaded)  # 输出 True
print("grape" in bf_loaded)  # 输出 False,可能是误判

在上面的示例中,我们首先创建了一个布隆过滤器,并向其中添加了一些元素。然后,我们检查了某些元素是否存在于布隆过滤器中。最后,我们将布隆过滤器保存到文件,并从文件中加载了一个新的布隆过滤器,并再次检查了元素的存在性。

请注意,布隆过滤器可能会产生误判,因此在实际应用中,你需要根据具体情况选择合适的预期容量和误差率来平衡存储空间和误判率。


三种数据删除策略

当我们需要管理缓存或者存储一些临时数据时,常常需要一些策略来决定何时删除数据。LRU、LFU 和 TTL 是三种常见的数据删除策略,它们各自适用于不同的场景,下面我会详细介绍它们的工作原理和应用场景。

LRU (Least Recently Used)

LRU(最近最少使用)是一种缓存淘汰算法,其核心思想是基于时间局部性原理,即最近被访问过的数据在未来也有可能被再次访问。LRU 算法认为,最近使用过的数据可能在未来会被再次使用,因此当缓存空间不足时,应该优先淘汰最久未被使用的数据。

工作原理:
  1. 当一个数据被访问时,LRU 算法会将这个数据移到队列的头部(或者说置于队列的最近位置)。
  2. 当缓存空间不足时,LRU 算法会淘汰队列尾部(或者说队列最久未被访问的)的数据。
应用场景:
  1. Web 缓存:缓存网页、图片等资源,以加快用户访问速度。
  2. 数据库缓存:缓存查询结果,以减少数据库访问次数。
  3. 页面置换算法:操作系统中,当内存不足时,用于选择合适的页面进行置换。

LFU (Least Frequently Used)

LFU(最不经常使用)是一种缓存淘汰算法,其核心思想是基于访问局部性原理,即经常被访问的数据在未来仍然有可能频繁被访问。LFU 算法认为,被访问次数较少的数据可能是不常用的数据,因此当缓存空间不足时,应该优先淘汰访问次数较少的数据。

工作原理:
  1. 当一个数据被访问时,LFU 算法会增加该数据的访问计数。
  2. 当缓存空间不足时,LFU 算法会淘汰访问计数最低的数据。
应用场景:
  1. 缓存系统:适用于需要根据数据的使用频率来进行淘汰的场景,如热点数据缓存。
  2. 数据库管理:可以用于缓存数据库查询结果,根据查询的频率来进行数据淘汰。

TTL (Time To Live)

TTL(生存时间)是一种设置键的生存时间的机制,常用于缓存系统中。当键的生存时间超过指定的时间后,键会被自动删除。

工作原理:
  1. 当设置一个键的 TTL 后,系统会记录键的创建时间。
  2. 当访问这个键时,系统会检查当前时间与键的创建时间之间的时间差是否超过 TTL。
  3. 如果超过 TTL,则系统会自动删除该键。
应用场景:
  1. 缓存系统:可以根据数据的更新频率来设置 TTL,保证缓存数据的时效性。
  2. 会话管理:在 Web 开发中,可以使用 TTL 来设置用户会话的有效期,保证安全性。

总的来说,LRU、LFU 和 TTL 是常用的缓存淘汰策略,根据具体的场景和需求选择合适的策略可以有效提高缓存系统的效率和性能。

设置 LRU、LFU 和 TTL 通常需要根据具体的缓存系统或应用程序来进行配置。下面我将简要介绍如何设置这三种策略:

LRU (Least Recently Used)

  1. 基于数据结构的实现:在使用数据结构实现缓存时,可以利用双向链表和哈希表结合的方式来实现LRU。在每次访问缓存数据时,将该数据移到链表的头部。当缓存空间不足时,淘汰链表尾部的数据。

  2. 缓存框架的配置:如果使用的是一些成熟的缓存框架,如Redis、Memcached等,通常会提供配置选项来设置LRU淘汰策略。在Redis中,可以通过配置maxmemory-policy选项为"volatile-lru"或"allkeys-lru"来启用LRU淘汰策略。

LFU (Least Frequently Used)

  1. 基于数据结构的实现:LFU通常使用哈希表和最小堆来实现。哈希表用于存储键值对,最小堆用于按照访问频率维护数据。当一个数据被访问时,更新其在最小堆中的访问频率,以便在淘汰时能够找到访问频率最低的数据。

  2. 缓存框架的配置:与LRU类似,一些缓存框架提供了配置选项来启用LFU淘汰策略。在Redis中,可以通过配置maxmemory-policy选项为"volatile-lfu"或"allkeys-lfu"来启用LFU淘汰策略。

TTL (Time To Live)

  1. 设置缓存数据的生存时间:在存储数据时,设置每个键的生存时间。在Redis中,可以使用SET命令的EXPIRE选项或者SETEX命令来设置键的生存时间。

  2. 定期清理过期数据:定期检查缓存中的数据,将已经过期的数据进行清理。在Redis中,可以使用定时任务或者使用Redis自带的过期键删除机制来清理过期数据。

以上是一些常见的设置方法,具体实现方式会根据具体的缓存系统或应用程序而有所不同。在配置时,还需要考虑到系统的性能和实际需求,选择合适的策略和参数。

哈希槽

Redis 哈希槽(Redis Cluster Slot)是在 Redis 集群中用于分片的一种机制。Redis 集群通过哈希槽将数据分散存储在多个节点上,实现了水平扩展和负载均衡。

详细介绍:

  1. 哈希槽的概念

    • Redis 将整个数据集分成 16384 个哈希槽,每个槽都有一个唯一的编号,从 0 到 16383。
    • 每个键都会被哈希到一个特定的槽上,根据键计算得出的哈希值被取模 16384 后所得到的值就是对应的槽号。
  2. 数据分片

    • 在 Redis 集群中,每个节点负责管理一部分哈希槽。每个节点都知道自己负责的哈希槽范围。
    • 当执行写操作时,客户端会计算键的哈希值,并根据哈希槽范围将数据发送到相应的节点上。
    • 通过将数据分散存储在多个节点上,实现了数据的分布式存储和负载均衡。
  3. 哈希槽的迁移

    • 当 Redis 集群中的节点数量发生变化或者某个节点负载过高时,会触发哈希槽的迁移。
    • 哈希槽迁移过程中,会将槽上的数据从源节点迁移到目标节点,确保数据的平衡分布。
    • 在迁移过程中,集群仍然可以对外提供读写服务,不会中断客户端的访问。
  4. 哈希槽的管理

    • Redis 集群提供了命令和 API 来管理哈希槽,包括手动设置槽的范围、获取节点的槽分布情况、触发槽的迁移等操作。
    • 通过这些管理工具,管理员可以灵活地管理集群的数据分布,以满足不同的需求。

使用场景:

  • 水平扩展:通过将数据分散存储在多个节点上,实现了数据的水平扩展,可以处理更大规模的数据量和请求。
  • 负载均衡:哈希槽的分布式存储机制使得集群中的数据负载得以均衡,提高了系统的整体性能和可用性。
  • 数据迁移:当集群中的节点发生变化或者负载不均时,可以通过哈希槽的迁移来重新平衡数据,保持集群的稳定运行。

总的来说,Redis 哈希槽是 Redis 集群中实现数据分片和负载均衡的关键机制,通过合理管理哈希槽,可以实现高效稳定的分布式存储服务。

分布式锁

Redis 分布式锁是在 Redis 数据库中利用原子操作和特定的数据结构来实现的一种分布式锁机制,用于解决分布式系统中多个客户端对共享资源的并发访问问题。下面是详细介绍:

工作原理:

  1. 获取锁

    • 客户端通过执行一段原子操作(通常是 SETNX 命令)来尝试获取锁,如果锁的键不存在,则设置锁的键,并设置一个过期时间,以防止锁忘记释放导致死锁。
    • 如果获取锁成功,则表示该客户端获得了对共享资源的独占访问权;如果获取失败,则表示锁已经被其他客户端持有。
  2. 释放锁

    • 当客户端使用完共享资源后,需要执行释放锁的操作。客户端通过删除锁的键来释放锁。
    • 释放锁时,需要确保释放的是自己持有的锁,以防止误释放其他客户端持有的锁。
  3. 锁的续约

    • 在某些情况下,客户端可能需要持有锁的时间比较长,但是锁的过期时间可能较短,这时客户端可以通过重设过期时间的方式来实现锁的续约,保证自己持有锁的时间不会过期。

特点和注意事项:

  • 互斥性:Redis 分布式锁能够保证在任意时刻只有一个客户端持有锁,确保了对共享资源的互斥访问。
  • 可重入性:同一个客户端在持有锁期间可以多次获取同一把锁,即可重入。
  • 阻塞和非阻塞模式:获取锁的操作可以是阻塞的(即客户端一直等待直到获取到锁)或者是非阻塞的(即尝试获取一次后立即返回结果)。
  • 锁的超时和续约:锁可以设置过期时间,在获取锁后需要及时释放,避免出现死锁情况;同时可以通过续约操作来延长锁的持有时间。
  • 误删问题:在释放锁时,需要确保删除的是自己持有的锁,防止误删其他客户端持有的锁。
  • 性能影响:使用 Redis 分布式锁可能会对 Redis 服务器的性能产生一定影响,特别是在高并发的情况下,需要注意锁的使用方式和频率,以避免对系统性能造成过大的影响。

使用场景:

  • 分布式系统中对共享资源的并发访问控制。
  • 实现任务队列的消费者竞争模式。
  • 数据库操作的乐观锁机制。

总的来说,Redis 分布式锁是一种简单有效的分布式锁实现方式,通过合理地使用和管理,可以解决分布式系统中的并发访问问题。


缓存穿透

缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。比如某个攻击者故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库上,使得数据库由于性能的原因崩溃,导致系统异常

缓存穿透问题产生
在这里插入图片描述

使用布隆过滤器
布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中

具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程
在这里插入图片描述

缓存雪崩

缓存雪崩描述的就是这样一个简单的场景:缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求

在这里插入图片描述

解决办法
针对 Redis 服务不可用的情况:

采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用限流,避免同时处理大量的请求

针对热点缓存失效的情况:

  • 设置不同的失效时间比如随机设置缓存的失效时间
  • 缓存永不失效

缓存击穿

如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题

击穿其实可以看做是雪崩的一个子集,解决方法一般有两种,设置热点数据永不过期和设置互斥锁

所谓的互斥锁,就是保证同一时间只有一个业务线程更新缓存,对于没有获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值

在这里插入图片描述

如何保证缓存和数据库的数据一致性

其实这是一个非常庞大且复杂的问题,根本不是一两句话能够说清楚的,如果要完全规避一致性问题,那么整个系统也会变得非常复杂

一般来说可以进行如下处理:更新 DB,然后删除 cache

当然此时有可能遇到更新 DB 成功,但是删除 cache 失败的情况,处理办法大致有两种:

缓存失效时间变短(不推荐,治标不治本) :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用
增加 cache 更新重试机制(常用):如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值