Redis面试题

为什么使用 Redis

  1. 速度快,因为数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O1)
  2. 支持丰富数据类型,支持 string,list,set,Zset,hash 等
  3. 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
  4. 丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除

Redis的常用数据类型有哪些

Redis是一个基于内存的键值对数据库,它的键都是字符串类型,而值的部分支持5种数据类型,每种类型特点不一样

  • string:字符串类型,可以存储普通字符串、JSON字符串,也可以存储对象系列化之后的字符串
  • hash:哈希类型,类似于Java中的HashMap,比较适合存储对象
  • list:列表类型,底层是一个顺序链表,可以从两端添加或移除元素,元素是有序的,可重复的
  • set:无序集合,没有重复元素
  • zset:有序集合,没有重复元素,且集合中每个元素关联一个分数,可以根据分数进行排序

跳表你了解吗

跳表(SkipList)首先是链表,但与传统的链表相比有几点差异:

  • 跳表结合了链表和二分查找的思想
  • 元素按照升序排列存储
  • 节点可能包含多个指针,指针跨度不同
  • 查找时从顶层向下,不断缩小搜索范围
  • 整个查询的复杂度为 O ( log n )

Redis数据类型Sorted Set使用了跳表

Redis 可以用来做什么

  • 缓存
  • 排行榜
  • 分布式计数器
  • 分布式锁
  • 消息队列
  • 分布式 token
  • 限流

Redis 为什么快

  • 内存操作)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
  • 单线程,省去线程切换、锁竞争的开销)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  • NIO 的 IO 多路复用模型)使用多路 I/O 复用模型,非阻塞 IO;这里"多路"指的是多个网络连接,"复用"指的是复用同一个线程

解释一下I/O多路复用模型

嗯~~

  • I/O多路复用是指利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。
  • 其中Redis的网络模型就是使用I/O多路复用结合事件的处理器来应对多个Socket请求,比如,提供了连接应答处理器、命令回复处理器,命令请求处理器;
  • 在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程

Redis 过期删除策略

常用的过期数据的删除策略就两个:

  1. 惰性删除:当客户端尝试访问一个键时,Redis会在访问时检查键是否过期,如果过期则执行删除操作。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
  2. 定期删除:每隔一段时间抽取一批 key 执行删除过期 key 操作。并且Redis底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影 响。

定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是定期删除+惰性/懒汉式删除。但是仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的 情况。这样就导致大量过期 key 堆积在内存里,然后就 Outofmemory 了。

怎么解决这个问题呢?答案就是:Redis 内存淘汰机制。

Redis 内存淘汰策略

Redis 提供6 种数据淘汰策略:

  • volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。

4.0 版本后增加以下两种

  • volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  • allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中移除最不经常使用的key

Redis 持久化机制 RDB 和 AOF 区别?

  • RDB是一个快照文件,它是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。
  • AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据

在恢复速度上:

  • RDB因为是二进制文件,在保存的时候体积也是比较小的,它恢复的比较快,但是它有可能会丢数据,我们通常在项目中采用RDB+AOF
  • 这段按照项目来说.....也会使用AOF来恢复数据,虽然AOF恢复的速度慢一些,但是它丢数据的风险要小很多,在AOF文件中可以设置刷盘策略,我们当时设置的就是每秒批量写入一次命令。

你们的项目中的持久化是如何配置选择的?

RDB+AOF

Redis 如何选择合适的持久化方式

  • 如果是数据不那么敏感,且可以从其他地方重新生成补回的,那么可以关闭持久化。
  • 如果是数据比较重要,不想再从其他地方获取,且可以承受数分钟的数据丢失,比如缓存等,那么可以只使用 RDB。
  • 如果是用做内存数据库,要使用 Redis 的持久化,建议是 RDB 和 AOF 都开启,或者定期执行 bgsave 做快照备份,RDB 方式更适合做数据的备份,AOF可以保证数据的不丢失

Redis4.0 相对与3.X 版本其中一个比较大的变化是4.0 添加了新的混合持久化方式

简单的说:新的 AOF 文件前半段是 RDB 格式的全量数据后半段是 AOF 格式的增量数据

优势:

  • 混合持久化结合了 RDB 持久化和 AOF 持久化的优点,由于绝大部分都 是 RDB 格式,加载速度快,同时结合 AOF,增量的数据以 AOF 方式保存 了,数据更少的丢失。!

劣势:

  • 兼容性差,一旦开启了混合持久化,在4.0 之前版本都不识别该 aof 文件,同时由于前部分是 RDB 格式,阅读性较差。

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

Redis在进行RDB期间是可以同时处理写请求的,这是因为Redis使用操作系统的多进程写时复制技术来实现快照持久化

具体说,就是Redis在持久化时会产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求

但当主线程执行写指令修改某个数据时,这个数据会被复制出一份副本,而子进程写入RDB 文件的内存数据只能是原本的内存数据。此时这次修改的数据只能交由下一次bgsave快照进行备份。

但RDB快照文件体积小,在恢复速度来说比AOF文件要快。

Redis集群有哪些方案, 知道嘛

在Redis中提供的集群方案总共有三种

  1. 主从复制
    1. 保证高可用性
    2. 实现故障转移需要手动实现
    3. 无法实现海量数据存储
  1. 哨兵模式
    1. 保证高可用性
    2. 可以实现自动化的故障转移
    3. 无法实现海量数据存储
  1. Redis分片集群
    1. 保证高可用性
    2. 可以实现自动
    3. 可以实现海量

什么是 Redis 主从同步

  1. 主从第一次同步是全量同步

  2. 后期数据变化后,则执行增量同步

全量同步的流程

  1. 从节点执行replicaof命令,发送自己的replid和offset给主节点
  2. 主节点判断从节点的replid与自己的是否一致,
  3. 如果不一致说明是第一次来,需要做全量同步,主节点返回自己的replid给从节点
  4. 主节点开始执行bgsave,生成rdb文件
  5. 主节点发送rdb文件给从节点,再发送的过程中
  6. 从节点接收rdb文件,清空本地数据,加载rdb文件中的数据
  7. 同步过程中,主节点接收到的新命令写入从节点的写缓冲区(repl_buffer)
  8. 从节点接收到缓冲区数据后写入本地,并记录最新数据对应的offset后期采用增量同步

增量同步的流程

  1. 主节点会不断把自己接收到的命令记录在repl_baklog中,并修改offset
  2. 从节点向主节点发送psync命令,发送自己的offset和replid
  3. 主节点判断replid和offset与从节点是否一致
  4. 如果replid一致,说明是增量同步。然后判断offset是否一致
  5. 如果从节点offset小于主节点offset,并且在repl_baklog中能找到对应数据则将offset之间相差的数据发送给从节点
  6. 从节点接收到数据后写入本地,修改自己的offset与主节点一致

如何从repl_baklog中获取offset(偏移量)后的数据

repl_baklog本质上是一个数组,但它是一个环形数组。

如果master未超出环的上限,slave就可以通过向master发生offset做增量同步。

如果master超出环的上限,就会从原点,也就是起点开始覆盖原有的数据,此时无法通过offset做增量同步。但可以从内存中找数据做全量同步

增量同步的风险

repl_baklog大小有上限,写满后会覆盖最早的数据。如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步。

repl_baklog可以在配置文件中进行修改存储大小

Redis分片集群中数据是怎么存储和读取的

Redis 集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,取到每个key的hash值对 16384取余来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。

Redis 集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key通过CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分hash 槽。

Redis 主从架构数据会丢失吗,为什么?

有两种数据丢失的情况:

  • 异步复制导致的数据丢失:因为 master -> slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这些部分数据就丢失了。
  • 脑裂导致的数据丢失:某个 master 因为网络故障,导致和其他slave不能连接,但是实际上 master 还运行着,此时哨兵可能就会认为master 宕机了,然后开启选举,将其他 slave 切换成了 master。此时,集群里就会存在两个 master,也就是所谓的脑裂。这时虽然某个 slave 被切换成了 master,但是可能 client 还没来得及切换到新的 master,还继续写向旧 master 的数据可能也丢失了。因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master复制数据。数据就会丢失。

如何保存 Redis 数据与 DB 一致

方案1:同步双写,即更新完 DB 后立即同步更新 redis

方案2:异步监听,即通过 Canal 监听 MySQL 变化的表,同步更新数据到Redis

方案3:MQ 异步,即更新完 DB 后生产消息到 MQ,MQ 消费者更新数据到Redis

什么是Redis的缓存预热

缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

如果不进行预热,那么 Redis 初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中,对数据库造成流量的压力。

缓存预热解决方案:

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

什么是缓存降级

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

  • 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
  • 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
  • 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
  • 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

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

  • 给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求,这些请求肯会瞬间把DB击垮

解决方案

  1. 使用互斥锁:当检测到某个热点数据的缓存失效时,不立即去查询数据库,而是先用Redis的setnx去设置一个互斥锁,当互斥锁添加成功之后,再进行数据库的查询和回设缓存,否则重试(get缓存的方法)获取缓存。避免大量请求同时访问数据库,减轻数据库的压力,可以保证数据的强一致性,但性能差
  2. 逻辑过期策略:对热点的key不设置过期时间,在存储数据的时候新增一个过期时间字段,在新增数据到缓存的时候维护这个字段值。当查询缓存时,会从redis取出数据,判断过期时间字段是否过期,如果过期,就添加一个互斥锁,并打开一个新线程,进行缓存重建,当前的线程无需等待,直接返回过期数据,同一时间内来查询缓存的线程,会获取互斥锁失败,直接返回的过期数据,只有新线程缓存重建完成,写入最新缓存并释放锁之后,查询到的缓存才是最新数据。高可用,性能好,但无法保证数据的绝对一致。

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

缓存穿透

  • 查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库

解决方法

  1. 缓存空数据,查询返回的数据为空,仍把这个空结果进行缓存。实现简单,消耗内存,可能发生不一致问题
  2. 布隆过滤器:使用布隆过滤器来预先过滤掉一些明显不存在的数据,从而减少对数据库的查询请求。如果布隆过滤器认为某个数据不存在,就可以直接返回空值,而不需要再次查询数据库。内存占用少,没有多余的key,实现复杂,存在误判
  3. 热点数据预热:将热点数据提前加载到缓存中,这样可以减少对数据库的请求,提高缓存命中率。
  4. 异常数据处理:针对异常情况,比如非法输入或恶意攻击,可以在应用层进行合法性校验,避免将非法请求传递到缓存层和数据库层。

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

  • 同一时段大量的缓存key同时失效或者Redis宕机,导致大量请求到达数据库,就会导致雪崩
  • 解决方案
    1. key同时失效:给不同的key的设置不同的过期时间(给key的TTL添加随机值)
    2. Redis宕机:搭建Redis的高可用集群,提高服务的可用性。例如哨兵模式,集群模式
    3. 给缓存业务设置降级限流策略。在nginx或springCloud gateway设置
    4. 给业务添加多级缓存。例如Guava或者Caffeine来实现一级缓存,Redis为二级缓存

缓存穿透、缓存击穿、缓存雪崩通用的解决方案

  1. 限流和降级:对请求进行限流和降级处理,当检测到某个请求频率异常高时,可以暂时拒绝服务或返回默认值,避免对数据库造成过大压力。

Redis分布式锁如何实现

Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则 SET)的简写。

上面这几个命令就是最基本的用来完成分布式锁的命令

加锁:使用 setnx key value 命令,如果key不存在,设置value(加锁成功)。如果已经存在lock(也就是有客户端持有锁了),则设置失败(加锁失败)。

解锁:使用 del 命令,通过删除键值释放锁。释放锁之后,其他客户端可以通过setnx命令进行加锁。

Redis分布式锁的有效时长

有效时间设置多长,假如我的业务操作比有效时间长?我的业务代码还没执行完就自动给我解锁了,不就完蛋了吗。

解决方案:

  1. 第一种:程序员自己去把握,预估一下业务代码需要执行的时间,然后设置有效期时间比执行时间长一些,保证不会因为自动解锁影响到客户端业务代码的执行。
  2. 第二种:给锁续期。锁续期实现思路:当加锁成功后,同时开启守护线程,默认有效期是用户所设置的,然后每隔10秒就会给锁续期到用户所设置的有效期,只要持有锁的客户端没有宕机,就能保证一直持有锁,直到业务代码执行完毕由客户端自己解锁,如果宕机了自然就在有效期失效后自动解锁

上述的第二种解决方案可以使用redis官方所提供的Redisson进行实现

Redisson分布式锁原理

Redisson是一个基于Redis的分布式锁框架,它的原理如下:

  1. 获取锁:
    • 客户端使用Redisson获取分布式锁时,会向Redis服务器发送一个SET命令,将锁的key设置为一个唯一标识符,并设置一个过期时间。
    • 如果锁的key在Redis中不存在,即锁未被其他客户端持有,那么客户端成功获取锁,并设置一个过期时间,表示锁的持有时间。
    • 如果锁的key在Redis中已经存在,表示锁已经被其他客户端持有,客户端获取锁失败,可以选择等待或进行重试。
  1. 保持锁的持有:
    • 客户端成功获取锁后,可以通过续期机制来保持锁的持有。续期机制是通过定时向Redis服务器发送一个命令来更新锁的过期时间,确保锁不会过期。
    • Redisson使用了Lua脚本来保证续期操作的原子性,避免了续期过程中锁被其他客户端获取的问题。
  1. 释放锁:
    • 当客户端不再需要持有锁时,可以向Redis服务器发送一个DEL命令,将锁的key从Redis中删除,释放锁资源。
    • 如果客户端持有的锁过期了,Redis会自动将该锁删除,避免锁的永久持有。

需要注意的是,Redisson还提供了一些额外的特性,如可重入锁、公平锁、红锁等,以满足不同的分布式锁需求。

总的来说,Redisson通过利用Redis的原子性操作和过期时间特性,实现了一个可靠的分布式锁机制,使得在分布式环境下可以安全地进行锁的获取、持有和释放。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值