2024最新!最全!面试必看!!!【Redis面经】

avatar
作者
筋斗云
阅读量:0

文章目录

Redis是什么?为什么要使用它?

Redis是key-value结构的NoSQL型数据库,由于它是存在内存中的,所以它的读写速度会很快,可以满足一些比较高频的访问。这也是我们选择使用它的原因之一。

Redis为什么这么快

  • 基于内存实现
  • 高效的数据结构
  • 合理地数据编码
  • 合理的线程模型
  • 虚拟内存机制

Redis的存储结构有哪些?有哪些应用场景?

字符串 String

解释:最基础的数据类型,可以存储任何形式的文本或二进制数据

应用场景:缓存、计数器、短信验证码

Hash

解释:存储键值对,类似于Java中的Map

应用场景:存储用户信息、对象

List列表

解释:相当于链表

应用场景:消息队列、任务队列

Set集合

解释:用于存储一些无序且唯一的元素集合

应用场景:用户关注列表,共同好友关注

ZSet集合

解释:有序、 不可重复,有权重参数

应用场景:排行榜

Redis分布式锁是什么?

是一种机制,确保在分布式系统中,多个节点在同一时刻只能有一个节点对共享资源进行操作。

解决在分布式环境下并发控制和数据一致性问题的关键技术之一

秒杀下单、抢红包等等业务场景,都需要用到分布式锁,

实现方式

  1. setnx + expire

先用setnx抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记释放

缺点:加锁与设置过期时间是非原子操作,如果加锁后未来得及设置过期时间系统异常,会导致其他线程永远获取不到锁

  1. setnx + value(系统过期时间 + 过期时间)

把过期时间放在setnx的value值里面,如果加锁失败,再拿出value值校验一下

优点:避免expire单独设置过期时间,把过期时间放到setnx的value值里面

缺点:

  • 过期时间是客户端自己生成的,必须要求分布式情况下,每个客户端的时间必须同步
  • 如果锁过期的时候,并发多个客户端同时请求过来,都执行jedis.getSet(),最终只能有一个客户端加锁成功,但是该客户端的过期时间,可能被别的客户端覆盖
  • 该锁没有保存持有者的唯一标识,可能被别的客户端释放/解锁
  1. 使用lua脚本(包含setnx + expire两条指令)

将结果放在字符串里,用jedis提交

  1. set的扩展命令(set ex px nx)

NX:表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取

EX sends:设定key的过期时间,时间单位是秒

PX milliseconds:设定key的过期时间,时间单位是毫秒

XX:仅当key存在时设置值

缺点:

  1. 锁过期释放了,业务还没执行完。假设线程a获取锁成功,一直在执行临界区的代码。但是100s过去后,它还没执行完。但是,这时候锁已经过期了,此时线程b又请求过来了。显然线程b就可以获取锁成功,也开始执行临界区的代码。那么问题就来了,临界区的业务代码都不是严格串行执行的了

解决方案:将过去时间设置的相对长一点

  1. 锁被别的线程误删了。假设线程a执行完后,去释放锁。但是不知道当前的锁可能是线程b持有的(线程a去释放锁时,有可能过期时间已经到了,此时线程b进来占有了锁)。那线程a就把线程b的锁释放掉了,但是线程b临界区业务代码可能都还没执行完呢

解决方案:将判断是不是当前线程加的锁和释放锁

  1. 开源框架 redisson

底层原理是,在其中一个线程加锁成功后,会启动一个watch dog 看门狗,它是一个后台线程,会每隔一段时间检查一下,如果线程还持有锁,那么就不断延长锁key的生存时间(看门狗的检查时间大概是锁持续时间的三分之一)

  1. 多机实现的分布式锁Redlock(集群模式下的分布式锁)

Redisson使用 Redlock 算法来保证在分布式环境下的锁的正确性和可靠性

Redis分布式锁的特征

  1. 互斥性:任意时刻,只有一个客户端能持有锁
  2. 锁超时释放:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁
  3. 可重入性:一个线程如果获取了锁之后,可以再次对其请求加锁
  4. 高性能和高可用:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效
  5. 安全性:锁只能被持有的客户端删除,不能被其他客户端删除

redis跳表是什么?

  • 跳跃表是有序集合zset的底层实现之一
  • 跳跃表支持平均O(logN),最坏 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。
  • 跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点。
  • 跳跃表就是在链表的基础上,增加多级索引提升查找效率。

为什么不用b树而选择跳表?

跳表在实现上的简洁性、插入和删除的高效性、以及内存消耗的合理性。跳表在 Redis 的有序集合操作中表现出色,符合 Redis
追求高性能和简单实现的设计理念。

Redis的底层原理

数据结构

内存管理

  • 对象复用和共享:Redis 会对一些常见的对象进行共享和复用,以减少内存开销。
  • 内存压缩:对于某些数据类型,Redis 会采用压缩技术来减少内存占用。
  • 惰性释放:在某些情况下,Redis 会延迟释放内存以提高性能。

持久化

  • 快照 (RDB) 持久化:
    • 将数据在某个时间点保存到二进制文件中。这种方式会定期进行,适合于灾难恢复,但可能会丢失最近一次快照之后的数据。
  • 追加日志 (AOF) 持久化:
    • 记录每次写操作到日志文件中。Redis 可以通过重放日志文件来恢复数据。AOF 提供更强的数据安全性,因为它能够更频繁地进行同步操作。

网络模式

  • 单线程:Redis 主要采用单线程来处理客户端请求,避免了多线程编程的复杂性和锁竞争问题。
  • 事件驱动:通过事件循环不断处理不同的 I/O 事件,包括读写事件、定时事件等。

高可用和集群

  • 主从复制:通过将数据从主节点复制到从节点,实现数据的冗余备份和读写分离。
  • 哨兵模式 (Sentinel):用于监控 Redis 主从架构,自动进行主从切换,提高系统的可用性。
  • Redis Cluster:支持分布式数据存储,通过分片机制将数据分布到多个节点上,实现水平扩展和高可用。

缓存淘汰策略(管理内存使用)

  • LRU (Least Recently Used):最少最近使用算法。
  • LFU (Least Frequently Used):最少使用频率算法。
  • TTL (Time to Live):基于时间的过期策略

内部实现细节

  • 字典 (Hash Table):用来实现字符串、哈希、集合等数据结构,使用渐进式 rehashing 来动态调整大小。
  • 跳表 (Skip List):用于实现有序集合,提供高效的范围查询和排序操作。
  • 双向链表 (Linked List):用于列表数据结构,支持快速的头尾操作。

管道和事务

  • 管道 (Pipeline):允许客户端一次发送多个命令,减少网络往返次数,提高性能。
  • 事务 (Transaction):通过 MULTI、EXEC、DISCARD 和 WATCH 命令实现简单的事务支持

redis的集群(Cluster)结构以及如何设置

Cluster是什么?

Redis Cluster时Redis的分布式实现,旨在解决单节点Redis在性能,存储和高可用性方面的限制,Redis
Cluster通过数据分片和自动故障转移来实现水平扩展和高可用性

  1. 数据分片(Sharding)
  • Cluster将数据分为16384个槽(hash slots)
  • 每个键通过CRC16哈希函数映射到一个槽
  • 槽均匀分布在集群的多个节点上
  1. 节点通信:
  • Cluster中的节点通过Gossip协议进行通信,定期交换状态信息
  • 节点之间通过PING/PONG消息了解彼此的健康状态和数据分布
  1. 故障转移
  • 如果主节点故障,集群中的从节点将被提升为主节点
  • 集群通过投票机制决定哪个从节点提升为主节点,确保高可用性
  1. 数据重分片
  • 当添加或移除节点时,Redis Cluster可以自动进行数据重分片,保证数据均匀分布

Cluster的优势

  1. 水平扩展:通过数据分片,可以将数据和请求分散到多个节点,支持大规模数据和高并发请求
  2. 高可用性:通过主从复制和自动故障转移,确保节点故障时的高可用性
  3. 无中心化:没有单节点故障,所有节点地位平等,增强系统的鲁棒性

Cluster的劣势

  1. 客户端复杂性:需要使用支持Cluster的客户端库,客户端需要处理定向和分片信息
  2. 运维复杂性:集群的管理和维护相对复杂,需要处理节点之间的通信、数据分片和故障转移
  3. 一致性保障:默认配置下、Cluster是最终一致性的,可能会存在短暂的数据不一致情况

redis缓存穿透问题

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

人话:读请求访问时,缓存和数据库都没有某个值,这样就会导致每次对这个值的查询请求都会穿透到数据库,这就是缓存穿透。

缓存穿透出现情况

  • 业务不合理的设计,比如大多数用户都没开守护,但是你的每个请求都去缓存,查询某个userid查询有没有守护。
  • 业务/运维/开发失误的操作,比如缓存和数据库的数据都被误删除了。
  • 黑客非法请求攻击,比如黑客故意捏造大量非法请求,以读取不存在的业务数据。

如何避免缓存穿透

  • 如果是非法请求,我们在API入口,对参数进行校验,过滤非法值。
  • 如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有有写请求进来的话,需要更新缓存哈,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。(业务上比较常用,简单有效)
  • 使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查

布隆过滤器原理:

它由初始值为0的位图数组和N个哈希函数组成。一个对一个key进行N个hash算法获取N个值,在比特数组中将这N个值散列后设定为1,然后查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该key存在。

redis 持久化机制

Redis提供了RDB和AOF两种持久化机制

RDB:快照持久化

快照就像给当前时刻的数据拍一张照,保存下来

具体实现:在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来恢复数据。

触发机制:

  1. 手动触发:
  • save:同步,会阻塞当前Redis服务器
  • bgsave: 异步,Redis进程执行fork操作创建子进程
  1. 自动触发:
  • save m n,m秒内数据集存在n次修改时,自动触发bgsave

RDB的优点

  • 适合大规模的数据恢复场景,如备份,全量复制等

RDB的缺点

  • 没办法做到实时持久化/秒级持久化。
  • 新老版本存在RDB格式兼容问题

AOF 日志持久化

采用日志的形式来记录每个写操作,追加到文件中,重启时再重新执行AOF文件中的命令来恢复数据。它主要解决数据持久化的实时性问题。默认是不开启的。

具体实现:

  1. 写命令处理:客户端发送写入命令到 Redis 服务器,Redis 将命令追加到内存中的 AOF 缓冲区。
  2. 同步策略:根据配置的同步策略,定期将 AOF 缓冲区内容同步到磁盘。
  3. AOF 重写:当 AOF 文件过大时,通过在后台生成新的 AOF 文件来压缩文件大小,并原子性地替换旧文件。
  4. 数据恢复:服务器启动时,通过读取和重放 AOF 文件中的命令来恢复数据库中的数据。

AOF的优点:

  • 数据的一致性和完整性更高

AOF的缺点:

  • AOF记录的内容越多,文件越大,数据恢复变慢。

Redis的缓存雪崩

解释: 指缓存中数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,引起数据库压力过大甚至down机。

  • 缓存雪奔一般是由于大量数据同时过期造成的,对于这个原因,可通过均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值,5小时+0到1800秒酱紫。
  • Redis 故障宕机也可能引起缓存雪奔。这就需要构造Redis高可用集群啦。

Redis的过期策略

我们在set key的时候,可以给它设置一个过期时间,比如expire key 60。指定这key60s后过期

定时过期

每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

惰性过期

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

定期过期

每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。

Redis中同时使用了惰性过期和定期过期两种过期策略。

假设Redis当前存放30万个key,并且都设置了过期时间,如果你每隔100ms就去检查这全部的key,CPU负载会特别高,最后可能会挂掉。

因此,redis采取的是定期过期,每隔100ms就随机抽取一定数量的key来检查和删除的。

但是呢,最后可能会有很多已经过期的key没被删除。这时候,redis采用惰性删除。在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会删除。

Redis的淘汰策略

  • volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰;
  • allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU(最近最少使用)算法进行淘汰。
  • volatile-lfu:4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU算法进行删除key。
  • allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰;
  • volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据;。
  • allkeys-random:当内存不足以容纳新写入数据时,从所有key中随机淘汰数据。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰;
  • noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。

什么是热key问题,如何解决

什么是热key

我们把访问频率高的key,称为热点key。

如果某一热点key的请求到服务器主机时,由于请求量特别大,可能会导致主机资源不足,甚至宕机,从而影响正常的服务。

热key是怎么产生的呢?

  • 用户消费的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
  • 请求分片集中,超过单Redi服务器的性能,比如固定名称key,Hash落入同一台服务器,瞬间访问量极大,超过机器瓶颈,产生热点Key问题

如何识别热key

  • 凭经验判断哪些是热Key;
  • 客户端统计上报;
  • 服务代理层上报

如何解决热key

  • Redis集群扩容:增加分片副本,均衡读流量;
  • 将热key分散到不同的服务器中;
  • 使用二级缓存,即JVM本地缓存,减少Redis的读请求

为什么Redis6.0之后改多线程

  • Redis6.0之前,Redis在处理客户端的请求时,包括读socket、解析、执行、写socket等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。
  • Redis6.0之前为什么一直不使用多线程?使用Redis时,几乎不存在CPU成为瓶颈的情况, Redis主要受限于内存和网络。例如在一个普通的Linux系统上,Redis通过使用pipelining每秒可以处理100万个请求,所以如果应用程序主要使用O(N)或O(log(N))的命令,它几乎不会占用太多CPU。

redis使用多线程并非是完全摒弃单线程,redis还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程。

这样做的目的是因为redis的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高redis的性能。

聊聊Redis事务机制

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

简言之,Redis事务就是顺序性、一次性、排他性的执行一个队列中的一系列命令。

事务的执行流程

  • 开始事务(MULTI)
  • 命令入队
  • 执行事务(EXEC)、撤销事务(DISCARD )

Redis的Hash冲突怎么办

什么是哈希冲突

通过不同的key,计算出一样的哈希值,导致落在同一个哈希桶中。

如何解决

Redis 会对哈希表做rehash操作,也就是增加哈希桶,减少冲突。为了rehash更高效,Redis还默认使用了两个全局哈希表,一个用于当前使用,称为主哈希表,一个用于扩容,称为备用哈希表。

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

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

  • 如果是save指令,会阻塞,因为是主线程执行的。
  • 如果是bgsave指令,是fork一个子进程来写入RDB文件的,快照持久化完全交给子进程来处理,父进程则可以继续处理客户端的请求。

Redis底层,使用的什么协议

RESP,英文全称是Redis Serialization Protocol,它是专门为redis设计的一套序列化协议. 这个协议其实在redis的1.2版本时就已经出现了,但是到了redis2.0才最终成为redis通讯协议的标准。

RESP主要有实现简单、解析速度快、可读性好等优点。

Redis的主从复制是什么

Redis的主从复制机制允许一个Redis实例(主节点)将其数据复制到一个或多个Redis实例(从节点)。这为数据的高可用性,负载均衡和读写分离提供了基础。主从复制在Redis是异步进行的,但可以配置为部分同步复制,以提高数据的一致性

主从复制如何实现

  1. 从节点连接主节点:当从节点启动并配置为从属于某个节点时,从节点会向主节点发送一个PSYNC命令,请求同步数据
  2. 初次全量复制:
  • 快照生成:主节点接受到同步请求后,会生成一个内存快照(RDB文件)并将其保存到磁盘
  • 快照传输:主节点将生成的RDB文件发送给从节点,从节点接受并加载这个快照文件,完成全量数据的同步
  • 命令缓冲区:在生成快照和传输快照期间
  1. 增量复制:
  • 命令传播:在全量复制完成后,主节点会将缓冲区中的写操作发送给从节点。从节点执行这些命令,使自己的数据状态与主节点保持一致
  • 持续同步:之后,主节点会将所有的写操作实时发送给从节点,从节点不断地应用这些命令,保持与主节点的数据一致性

主从复制的优势

  1. 高可用性:通过主从复制,可以在主节点故障时快速切换到从节点,从而提高系统的可用性
  2. 读写分离:可以将读操作分配给从节点,减轻主节点的负载,提高系统的整体性能
  3. 负载均衡:通过增加从节点,可以实现负载均衡,分担主节点的读请求压力
  4. 数据备份:从节点可以作为数据的实时备份,防止数据丢失

主从复制的缺点

  1. 数据一致性:Redis的主从复制是异步的,这意味着在主节点和从节点之间可能会有短暂的延迟,导致从节点的数据与主节点不一致。在极端的情况下,如果主节点在同步完成之前发生故障,可能会导致数据丢失
  2. 写操作性能瓶颈:所有写操作都必须在主节点上执行,这意味着主节点的写操作性能会成为系统的瓶颈,尤其是在写操作频繁的场景中
  3. 复制延迟:在网络延迟较大或写操作密集的情况下,从节点可能会落后于主节点,导致读操作返回的结果不及时
  4. 故障恢复复杂性:在主节点发生故障时,需要手动或者通过Redis Sentinel等工具进行故障转移

哨兵机制原理

Redis Sentinel是Redis的高可用性解决方案,它监控Redis主从复制集群中的实例,并在检测到的主节点故障时自动完成故障转移。

Sentinel的主要职责:

  1. 监控(Monitor):不停检查主从节点是否正常工作
  2. 通知(Notification):当检测到故障时,向系统管理员发送通知
  3. 自动故障转移(Automatic Failover):如果主节点故障,Sentinel会将一个从节点提升为主节点
  4. 配置提供者(Configuration Provider):Sentinel向客户端提供当前主节点的地址,客户端通过Sentinel获取主节点信息

哨兵机制(Sentinel)解决问题

  1. 高可用性:在主节点故障时,Sentinel可以自动完成故障转移,确保系统的高可用性
  2. 自动化管理:减少了人工干预,提高了系统的自动化管理水平
  3. 客户端透明性:客户端通过Sentinel获取当前主节点信息,无需关心底层节点变化

哨兵机制如何实现

  1. 监控
  • Sentinel定期通过PING命令检查Redis实例的健康状态
  • 如果一个实例在指定时间内没有响应,Sentinel将其标记为疑似故障(Subjectively Down,SDOWN)
  1. 故障判断
  • 多个Sentinel通过协商和投票,确定实例是否真正故障(Objectively Down,ODOWN)
  • 如果多数Sentinel同意主节点故障,开始故障转移
  1. 自动故障转移
  • 选择一个从节点作为新主节点
  • 通过SLAVEOF NO ONE命令将从节点提升为主节点
  • 将其他从节点指向新的主节点,重新建立复制关系
  • 更新配置,通知客户端新的主节点地址
  1. 通知和配置
  • Sentinel通过发布/订阅(Pub/Sub)机制向客户端和管理员发送通知
  • 客户端可以连接Sentinel获取当前主节点的地址,确保对最新主节点的读写操作

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!