Redis面试之常见问题


转载于:https://mp.weixin.qq.com/s/qvXm1pU8T_2mCZCjkTR7QA

1 Redis常见面试问题

1.1 Redis是单线程还是多线程

Redis不同版本之间采用的线程模型是不一样的,在Redis4.0版本之前使用的是单线程模型,在4.0版本之后增加了多线程的支持。

4.0之前虽然说Redis是单线程,也只是说它的网络I/O线程以及SetGet操作是由一个线程完成的。但是Redis持久化集群同步还是使用其他线程来完成。

4.0之后引入了多线程的支持,主要是体现在大数据的异步删除功能上,例如 unlink key、flushdb async、flushall async

随着底层网络硬件性能的提升,Redis 的性能瓶颈逐渐体现在网络 I/O 的读/写上,单个线程处理网络读/写的速度跟不上底层网络硬件执行的速度。读/写网络的读/写系统调用占用了 Redis 执行期间大部分 CPU 时间,所以 Redis 采用多个 I/O 线程来处理网络请求,提高网络请求处理的并行度
不过Redis6.0 版本开始正式宣布支持多线程模型,需要注意的是,RedisI/O 线程模型只用来处理网络读/写请求Redis读/写命令依然是单线程处理的。

1.2 使用单线程原因

那为什么Redis在4.0之前会选择使用单线程?而且使用单线程还那么快?

选择单线程主要是使用简单,不存在锁竞争,可以在无锁的情况下完成所有操作,不存在死锁和线程切换带来的性能和时间上的开销,但同时单线程也不能完全发挥出多核CPU性能

为什么单线程那么快主要有以下几个原因:

  • Redis的大部分操作都在内存中完成,内存中的执行效率本身就很快,并且采用了高效的数据结构,比如哈希表和跳表。
  • 使用单线程避免了多线程的竞争,省去了多线程切换带来的时间和性能开销,并且不会出现死锁。
  • 采用 I/O 多路复用机制处理大量客户端的Socket请求,因为这是基于非阻塞的 I/O 模型,这就让Redis可以高效地进行网络通信,I/O的读写流程也不再阻塞。
  • 很多的客户端连接先到linux中的内核,内核和redis中间使用的是epoll(非阻塞的多路复用),那些进程一笔一笔的进行的。

在分布式情况下,这个数据一致性很重要。每个连接里边命令是顺序到达、顺序处理的,但是如果说里面有个key,这个key为a,那么两个客户端发了一个对a的操作,那么无论从网络当中跳跃谁先到达的,或者指定谁先轮到谁了。那么其实这两个人对一个的操作,很难判定是谁先谁后,但是如果是你一个人,它里边线性,而且没有使用多线程,线程还是安全的,虽然它可以有多线程,但是线程安全,对a的操作,这边能控制住,先创建a再删除a,只要这边能操作的话,那么这个数据是可以保证的。如果是单线程,这个客户端就是一个线程,就是一个socket里面也是一个线程,那么这个线程肯定是先发出一个创建a再发出一个删除a,但是如果客户端里边是多线程,那么这里一个创建命令和删除命令,指不定谁跑到前面了,如果线程不是安全的话,那么有可能先把删除的发出去,再把创建的发出去。
请添加图片描述
点击了解Linux中epoll原理机制

1.3 Redis高可用

Redis实现高可用主要有三种方式:主从复制哨兵模式,以及 Redis 集群

1.3.1 主从复制

将从前的一台 Redis 服务器,同步数据到多台从 Redis 服务器上,即一主多从的模式,这个跟MySQL主从复制的原理一样。
在这里插入图片描述
点击了解redis 持久化中的主从复制同步

1.3.2 哨兵模式

1.3.2.1 简介

使用 Redis 主从复制的时候,会有一个问题,就是当 Redis 的主从服务器出现故障宕机时,需要手动进行恢复,为了解决这个问题,Redis 增加了哨兵模式(因为哨兵模式做到了可以监控主从服务器,并且提供自动容灾恢复的功能)。
它专注于对 Redis 实例(主节点、从节点)运行状态的监控,并能够在主节点发生故障时通过一系列的机制实现选主主从切换,实现自动故障转移,确保整个 Redis 系统的可用性。
在这里插入图片描述

sentinel 主要做四件事情:

  • 监控 masterslave 状态,判断是否下线。

    • 每秒一次的频率向 masterslave 以及其他 sentinel 发送 PING 命令,如果该节点距离最后一次响应 PING 的时间超过 down-after-milliseconds 选项所指定的值(即在设定的时间内没有回复心跳包), 则这个实例会被 Sentinel 标记为主观下线,当 master 被标记主观下线。
    • 其他正在监视这个 master 的所有 sentinel 会按照每秒一次的频率确认 master 是否主观下线。
    • 当超过配置的 quorum 数量的 sentinel 都认为 master 主观下线,则标记这个 master 客观下线。
  • 选举新 master,如果 master 出现故障,sentine 需要选举一个 slave 晋升为新 master。晋升为新 master 的 slave 是有条件的,先过滤不满足条件的,再打分排优先级。

    • 过滤掉下线、网络异常的 slave
    • 过滤掉经常与 master 断开的 slave
    • slave 优先级,通过 replica-priority 100 配置,值越高,优先级越高。
    • 若优先级相同,则复制偏移量(processed replication offset)最大的从节点,已复制的数据量越多越好,slave_repl_offsetmaster_repl_offset 差值越小。
    • slave runID,在优先级和复制进度都相同的情况下,runID 最小的 slave 得分最高,会被选为新主库。
  • 选举领导者哨兵(Leader Sentinel),执行主从切换,从 sentinel 集群中选举一个 leader 执行故障自动切换。
    成为 leader 的条件是收到足够的赞成票(大于等于quorum和(总节点数/2 + 1)的最大值)。
    第一个判定 master 主观下线的 sentinel 收到其他 sentinel 节点的回复并确定 master 客观下线后,就会给其他 sentinel 节点发送命令申请成为 leader。
    选举领导者哨兵而不是直接从从节点中选举新的主节点,主要是为了以下原因:

    • 协调一致性:通过选举领导者哨兵,可以确保在集群中仅有一个哨兵负责主从切换,避免多个哨兵同时进行切换操作导致的不一致性。
    • 集中决策:领导者哨兵集中管理整个主从切换过程,使得切换过程更加有序和可控。
    • 分担职责:哨兵负责监控和管理 Redis 节点,而从节点主要用于数据复制和故障切换。在发生故障时,选举领导者哨兵来管理切换过程,可以让从节点专注于数据同步,分担系统负载
  • 通知,通知其他 slave 执行 replicaof 与新的 master 同步数据,并通知客户端与新 master 建立连接。
    在这里插入图片描述

  • 哨兵如何通知客户端?
    选出新主节点后,哨兵的任务还没完。它需要告诉所有从节点客户端:主节点已经更换
    对于从节点,哨兵会自动修改它们的复制目标,对于客户端,哨兵会通过发布订阅通知它们新的主节点地址。
    哨兵会通过 Redis发布订阅系统,把新主节点的信息推送给所有订阅的客户端。
    例如,使用 SENTINEL get-master-addr-by-name mymaster 命令可以获取新主节点的 IP 和端口。

redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
# 返回新主节点的 IP 和端口,比如 [ "127.0.0.1", "6380" ]

1.3.2.2 哨兵机制的高可用性保障

一个哨兵单点可能会挂掉,所以 Redis 支持多哨兵协同工作。这种模式下,哨兵之间也会相互通信,通过 Raft 类似的选举机制选出一个领头哨兵Leader Sentinel),由它负责协调故障转移。

每个哨兵实例都可以彼此通信,确保在任何一个哨兵挂掉的情况下,系统仍然能正常工作。

哨兵机制是 Redis 提供的高可用解决方案,主要功能包括监控主从节点状态、进行故障转移以及通知客户端更新主节点信息。

  • 监控:哨兵通过 PING 命令定期检测主从节点是否存活,基于响应时间判断节点状态。
  • 选主:哨兵根据复制偏移量、优先级和网络延迟等标准选举新的主节点,保证数据一致性和快速恢复。
  • 高可用性:通过多个哨兵协同工作,避免单点故障。
  • 通知:哨兵利用发布订阅机制通知客户端主节点变更,确保业务无缝切换。

1.3.3 Redis Cluster(集群)

哨兵模式 基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。因此,Reids Cluster集群(切片集群的实现方案)应运而生,它在Redis3.0加入的,实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。并且,它可以保存大量数据,即分散数据到各个Redis实例,还提供复制和故障转移的功能。

Redis Cluster 是一种分布式去中心化的运行模式,是在 Redis 3.0 版本中推出的 Redis 集群方案,通过分片(sharding)来进行数据管理(分治思想的一种实践),并提供复制和故障转移功能,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,从而提高 Redis 服务的读写性能。

图片

使用哨兵模式在数据上有副本数据做保证,在可用性上又有哨兵监控,一旦master宕机会选举salve节点为master节点,那为什么还需要使用集群模式呢?
哨兵模式归根节点还是主从模式,在主从模式下我们可以通过增加salve节点来扩展读并发能力,但是没办法扩展写能力和存储能力,存储能力只能是master节点能够承载的上限。所以为了扩展写能力和存储能力,我们就需要引入集群模式。

集群中那么多Master节点,redis cluster在存储的时候如何确定选择哪个节点呢?
Redis Cluster采用的是数据分片实现节点选择的

Redis集群搭建

1.4 Redis内存(数据)淘汰策略

redis中,我们是可以去设置最大使用内存大小server.maxmemory的,当redis内存数据集大小上升到一定程度的时候,就会施行数据淘汰机制。
不同位数的操作系统,maxmemory 的默认值是不同的:

  • 在 64 位操作系统中,maxmemory 的默认值是 0,表示没有内存大小限制,那么不管用户存放多少数据到 Redis 中,Redis 也不会对可用内存进行检查,直到 Redis 实例因内存不足而崩溃也无作为。
  • 在 32 位操作系统中,maxmemory 的默认值是 3G,因为 32 位的机器最大只支持 4GB 的内存,而系统本身就需要一定的内存资源来支持运行,所以 32 位操作系统限制最大 3 GB 的可用内存是非常合理的,这样可以避免因为内存不足而导致 Redis 实例崩溃。

Redis提供了8种数据淘汰策略,分为不进行数据淘汰进行数据淘汰两类策略,
不进行数据淘汰的策略 noevictionRedis3.0之后,默认的内存淘汰策略),它表示当运行内存超过最大设置内存时,不淘汰任何数据,这时如果有新的数据写入,会报错通知禁止写入,不淘汰任何数据,但是如果没用数据写入的话,只是单纯的查询或者删除操作的话,还是可以正常工作。

  • no-enviction:禁止淘汰数据,如果redis写满了将不提供写请求,直接返回错误

进行数据淘汰的策略:

  • volatile-lru:从已经设置过期时间的数据集中,挑选最近最少使用的数据淘汰,即:最久未使用的键值
  • volatile-ttl:从已经设置过期时间的数据集中,挑选即将要过期的数据淘汰。
  • volatile-random:从已经设置过期时间的数据集中,随机挑选数据淘汰。
  • volatile-lfu:从已经设置过期时间的数据集中,会使用LFU算法选择设置了过期时间的键值对,即:最不常用的键值
  • allkeys-lru:从所有的数据集中,挑选最近最少使用的数据淘汰。
  • allkeys-random:从所有的数据集中,随机挑选数据淘汰。
  • allkeys-lfu:淘汰整个键值中最不常用的键值

附录:LRULFU是不同的:

  • LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面
  • LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页

使用策略规则:

  • 如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
  • 如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random

1.5 Redis过期键删除策略

Redis过期键删除策略:

  • 定时删除:在设置键的过期时间的同时,创建一个timer,让定时器在键的过期时间到达时,立即执行对键的删除操作。(主动删除)
    对内存友好,但是对cpu时间不友好,有较多过期键的而情况下,删除过期键会占用相当一部分cpu时间。
  • 惰性删除:放任过期键不管,但是每次从键空间中获取键时,都检查取到的键是否过去,如果过期就删除,如果没过期就返回该键。(被动删除)
    cpu时间友好,程序只会在取出键的时候才会对键进行过期检查,这不会在删除其他无关过期键上花费任何cpu时间,但是如果一个键已经过期,而这个键又保留在数据库中,那么只要这个过期键不被删除,他所占用的内存就不会释放,对内存不友好。
    这种方式在Redis中主要是通过expireIfNeeded函数来实现的
    Redis 4.0之后引入的,叫做lazyfree_lazy_expire,它决定了是否异步删除过期键。如果设置为1,表示使用异步删除,避免阻塞其他操作;如果设置为0,则会同步删除,删除操作会阻塞当前请求,直到删除完成
  • 定期删除:每隔一段时间就对数据库进行一次检查,删除里面的过期键。(主动删除)采用对内存cpu时间折中的方法,每隔一段时间就对一些 key 进行采样检查,检查是否过期,如果过期就进行删除
    定期删除的检查周期默认是每秒执行10次,可以通过hz配置来调整。默认情况下,hz值为10
    定期删除的逻辑是这样的:
    • 采样一定个数的key默认20),采样的个数可以进行配置,并将其中过期的 key 全部删除;
    • 如果过期 key 的占比超过可接受的过期 key 的百分比(默认25%),则重复删除的过程,直到过期key的比例降至可接受的过期 key 的百分比以下
    • 注意:为了防止定期删除过程中的循环过长Redis限制了每轮检查的最大时间,默认情况下不会超过25毫秒。如果在25毫秒内没有完成过期键的删除,Redis会强制退出当前轮次,等到下一轮继续处理。

1.6 Redis的key和value可以存储的最大值分别是多少

虽然Key的大小上限为512M,但是一般建议key的大小不要超过1KB,这样既可以节约存储空间,又有利于Redis进行检索。
value的最大值也是512M。对于String类型的value值上限为512M,而集合、链表、哈希等key类型,单个元素的value上限也为512M

1.7 Redis实现数据的去重

  • Redisset:它可以去除重复元素,也可以快速判断某一个元素是否存在于集合中,如果元素很多(比如上亿的计数),占用内存很大。
  • Redisbit:它可以用来实现比set内存高度压缩的计数,它通过一个bit设置为1或者0,表示存储某个元素是否存在信息。例如网站唯一访客计数,可以把user_id作为 bit 的偏移量 offset,如设置为1表示有访问,使用1 MB的空间就可以存放800多万用户的一天访问计数情况。
  • HyperLogLog:实现超大数据量精确的唯一计数都是比较困难的,HyperLogLog可以仅仅使用 12 k左右的内存,实现上亿的唯一计数,而且误差控制在百分之一左右。
  • bloomfilter布隆过滤器:布隆过滤器是一种占用空间很小的数据结构,它由一个很长的二进制向量和一组Hash映射函数组成,它用于检索一个元素是否在一个集合中

1.8 Redis序列化

Redis什么时候需要序列化?

  • 序列化:将 Java 对象转换成字节流的过程。
  • 反序列化:将字节流转换成 Java 对象的过程。
    图片

为什么需要序列化呢?
打个比喻:作为大城市漂泊的码农,搬家是常态。当我们搬书桌时,桌子太大了就通不过比较小的门,因此我们需要把它拆开再搬过去,这个拆桌子的过程就是序列化。而我们把书桌复原回来(安装)的过程就是反序列化啦。

比如想把内存中的对象状态保存到一个文件中或者数据库中的时候(最常用,如保存到redis);再比喻想用套接字在网络上传送对象的时候,都需要序列化。
RedisSerializer接口 是 Redis 序列化接口,用于 Redis KEYVALUE 的序列化,有如下序列化方式:

  • JDK 序列化方式 (默认)
  • String 序列化方式
  • JSON 序列化方式
  • XML 序列化方式

1.9 大key

1.9.1 定义

Redis 中的 大key 是指存储在Redis中的占用内存较大的键值对。大key可能会导致Redis的性能下降,因为大key占用的内存较多,需要较长的时间来进行读写操作。而且,当大key被删除时,会阻塞Redis的其他操作。

常见的大key包括:

  • 存储大量数据的字符串类型键值对。
  • 存储大量元素的列表、集合或有序集合。
  • 包含大量字段的哈希表。

1.9.2 大Key解决方案

为了避免大keyRedis性能的影响,可以采取以下措施:

  • 大key拆分为多个较小的键值对,以减少每个键值对的内存占用。
  • 使用分布式缓存,将大key分散到多个Redis实例上。
  • 使用压缩算法对大key进行压缩,减少内存占用。
  • 使用Redis的分片功能,将大key分散到多个分片上,减少单个Redis实例的负载。
  • 大Key进行清理。将不适用Redis能力的数据存至其它存储,并在Redis中删除此类数据。注意,要使用异步删除。
  • 监控Redis的内存水位。可以通过监控系统设置合理的Redis内存报警阈值进行提醒,例如Redis内存使用率超过70%、Redis的内存在1小时内增长率超过20%等。
  • 对过期数据进行定期清。堆积大量过期数据会造成大Key的产生,例如在HASH数据类型中以增量的形式不断写入大量数据而忽略了数据的时效性。可以通过定时任务的方式对失效数据进行清理。

1.10 热Key

1.10.1 定义

Redis热key是指在Redis中被频繁访问的键。当某个键被频繁访问时,它就被认为是热key热key通常是由于某些热门操作、热门数据或者高并发访问所导致的

通常以其接收到的 Key 被请求频率来判定,例如:

  • QPS集中在特定的Key:Redis实例的总QPS(每秒查询率)为10,000,而其中一个Key的每秒访问量达到了7,000。
  • 带宽使用率集中在特定的Key:对一个拥有上千个成员且总大小为1 MB的HASH Key每秒发送大量的HGETALL操作请求。
  • CPU使用时间占比集中在特定的Key:对一个拥有数万个成员的Key(ZSET类型)每秒发送大量的ZRANGE操作请求。

1.10.2 如何解决热key

热key可能对Redis的性能产生重大影响。当一个键被频繁访问时,Redis需要频繁地从内存中读取或写入该键的值,这可能导致内存带宽的瓶颈、CPU利用率的增加以及延迟的增加。此外,如果一个热key的值过大,可能会占用大量的内存,进一步影响Redis的性能
解决方案:

  • Redis集群架构中对热Key进行复制,数据分片
    Redis集群架构中,由于热Key的迁移粒度问题,无法将请求分散至其他数据分片,导致单个数据分片的压力无法下降。此时,可以将对应热Key进行复制并迁移至其他数据分片,例如将热Key foo复制出3个内容完全一样的Key并名为foo2、foo3、foo4,将这三个Key迁移到其他数据分片来解决单个数据分片的热Key压力。
  • 使用读写分离架构。
    如果热Key的产生来自于读请求,您可以将实例改造成读写分离架构来降低每个数据分片的读请求压力,甚至可以不断地增加从节点。但是读写分离架构在增加业务代码复杂度的同时,也会增加Redis集群架构复杂度。不仅要为多个从节点提供转发层(如Proxy,LVS等)来实现负载均衡,还要考虑从节点数量显著增加后带来故障率增加的问题。Redis集群架构变更会为监控、运维、故障处理带来了更大的挑战。
  • 使用合适的数据结构
    根据实际情况选择合适的数据结构,如列表、集合、有序集合等,来存储热key的值,以提高读写性能。
  • 缓存策略
    使用合理的缓存策略,比如设置合适的过期时间、使用LRU算法等,以减少对热key的访问频率。
  • 持久化策略
    根据业务需求选择合适的持久化方式,如RDB快照、AOF日志等,以确保数据的安全性和可靠性。

1.11 Redis中缓冲区

Redis中的缓冲区主要根据其功能和用途进行划分,可以归纳为以下几种:

  • 客户端缓冲区:
    • 输入缓冲区:缓存客户端发送过来的命令,Redis主线程从该缓冲区中读取命令进行处理。
    • 输出缓冲区:当 Redis主线程处理完数据后,将结果写入该缓冲区,再返回给客户端。
    • 缓存区溢出原因:
      • 写入了BigKey,即一次性写入了大量数据,超过了缓冲区的大小。
      • 服务端处理请求的速度过慢,导致无法及时处理请求,使得客户端发送的请求在缓冲区内越积越多。
  • 复制缓冲区:
    • 复制缓冲区:在全量复制过程中,主节点在向从节点传输RDB文件的同时,会继续接收客户端发送的写命令请求。这些写命令会先保存在复制缓冲区中,等RDB文件传输完成后,再发送给从节点去执行。
    • 复制积压缓冲区:在增量复制时,主节点和从节点进行常规同步时,会把写命令暂存在复制积压缓冲区中。
    • 溢出原因:
      • 主库传输RDB文件以及从库加载RDB文件耗时长,同时主库接收的写命令操作较多。
      • 缓冲区大小设置不合理。
  • AOF缓冲区:
    • AOF缓冲区:当Redis进行持久化时,会先将客户端传来的命令存放在AOF缓冲区,再根据具体的策略(always、everysec、no)去写入磁盘中的AOF文件中。
    • AOF重写缓冲区:在Redis进行AOF重写时,主进程会fork一个子进程进行AOF重写,此时主进程接收的指令会存放在AOF重写缓冲区中。当AOF重写完成后,这些指令会被追加到AOF文件中。
  • 内存级缓存:
    虽然不是严格意义上的缓冲区,但Redis的内存级缓存是其最常见的使用场景。通过将数据存储在内存中,减少读取数据库的频率,提高数据访问速度。
  • 其他内存使用:
    • Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右,used_memory在800KB左右。
    • 对象内存:存储着用户所有的数据,消耗可以简单理解为sizeof(keys)+sizeof(values)。
    • 内存碎片:正常的碎片率在1.03左右
posted @   上善若泪  阅读(746)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示