【Java面试题】Redis上篇(基础、持久化、底层数据结构)

发布于:2024-03-29 ⋅ 阅读:(21) ⋅ 点赞:(0)

基础

1.什么是Redis?

  1. Redis即 Remote Dictionary Service 三个单词中加粗字母的组合,是一种基于键值对(key-value)的 NoSQL 数据库。
  2. value值支持多种数据结构:string(字符串)、hash(哈希)、 list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、HyperLogLogopen in new window(基数估算)、GEO(地理信息定位)
  3. Redis 的所有数据都存放在内存当中,所以它的读写性能非常出色
  4. Redis 还可以将内存数据持久化到硬盘上,这样在发生类似断电或者机器故障的时候,内存中的数据并不会“丢失”

2.Redis可以用来干什么?

  1. 热点数据缓存
  2. 计数器(记录浏览量、点赞收藏量)
  3. 分布式锁(SETNX命令+过期时间+Lua脚本)
  4. 排行榜(有序集合)
  5. 社交网络(共同好友/喜好)

3.Redis的五种基本数据结构?

介绍 应用场景 底层数据结构
string 1. 字符串是最基础的数据结构
字符串(简单的字符串、复杂的字符串(例如 JSON、XML))
数字 (整数、浮点数)
甚至是二进制(图片、音频、视频),但最大不能超过 512MB。
1.缓存token
2.计数功能
hash 键值对集合,value = {name: ‘沉默王二’, age: 18} 1.缓存用户信息
2.缓存对象
list list 是一个简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边) 1.消息队列
2.文章列表
set 集合是字符串的无序集合,集合中的元素是唯一的,不允许重复。 1.共同好友
sorted set 有序集合 1.排序功能
2.点赞收藏统计

4.Redis为什么这么快?

Redis 的速度⾮常快,单机的 Redis 就可以⽀撑每秒十几万的并发,性能是 MySQL 的⼏⼗倍。速度快的原因主要有几点:

  1. 基于内存的数据存储,Redis 将数据存储在内存当中,使得数据的读写操作避开了磁盘 I/O。而内存的访问速度远超硬盘,这是 Redis 读写速度快的根本原因。
  2. 单线程模型,Redis 使用单线程模型来处理客户端的请求,这意味着在任何时刻只有一个命令在执行。这样就避免了线程切换和锁竞争带来的消耗
  3. 高效的数据结构,Redis 提供了多种高效的数据结构,如字符串(String)、列表(List)、集合(Set)、有序集合(Sorted Set)等,这些数据结构经过了高度优化,能够支持快速的数据操作。
  4. IO 多路复用,基于 Linux 的 select/epoll 机制。该机制允许内核中同时存在多个监听套接字和已连接套接字,内核会一直监听这些套接字上的连接请求或者数据请求,一旦有请求到达,就会交给 Redis 处理,就实现了所谓的 Redis 单个线程处理多个 IO 读写的请求。

5.什么是I/O多路复用?

引用知乎上一个高赞的回答来解释什么是 I/O 多路复用。假设你是一个老师,让 30 个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:

  • 第一种选择:按顺序逐个检查,先检查 A,然后是 B,之后是 C、D。。。这中间如果有一个学生卡住,全班都会被耽误。这种模式就好比,你用循环挨个处理 socket,根本不具有并发能力。
  • 第二种选择:你创建 30 个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者- 线程处理连接。
  • 第三种选择,你站在讲台上等,谁解答完谁举手。这时 C、D 举手,表示他们解答问题完毕,你下去依次检查 C、D 的答案,然后继续回到讲台上等。此时 E、A 又举手,然后去处理 E 和 A。

第一种就是阻塞 IO 模型,第三种就是 I/O 复用模型

多路复用模型

6.Redis6.0为什么使用了多线程?

  1. Redis6.0 的多线程是用多线程来处理数据的读写和协议解析,但是 Redis执行命令还是单线程的
  2. 这样做的⽬的是因为 Redis 的性能瓶颈在于⽹络 IO ⽽⾮ CPU,使⽤多线程能提升 IO 读写的效率,从⽽整体提⾼ Redis 的性能。

Redis6.0多线程

持久化

7.Redis的持久化方式?区别?

  1. Redis 支持两种主要的持久化方式:RDB(Redis DataBase)持久化和 AOF(Append Only File)持久化。

  2. 这两种方式可以单独使用,也可以同时使用。

  3. RDB

    • RDB 持久化通过创建数据集的快照(snapshot) 来工作,在指定的时间间隔内将 Redis 在某一时刻的数据状态保存到磁盘的一个 RDB 文件中。

    • 可通过 save 和 bgsave 命令两个命令来手动触发 RDB 持久化操作:

      • save 命令:会同步地将 Redis 的所有数据保存到磁盘上的一个 RDB 文件中。这个操作会阻塞所有客户端请求,直到 RDB 文件被完全写入磁盘

      • bgsave 命令:会在后台异步地创建 Redis 的数据快照,并将快照保存到磁盘上的 RDB 文件中。这个命令会立即返回,Redis 服务器可以继续处理客户端请求

        • 在 BGSAVE 命令执行期间,Redis 会继续响应客户端的请求,对服务的可用性影响较小。快照的创建过程是由一个子进程完成的,主进程不会被阻塞。是在生产环境中执行 RDB 持久化的推荐方式。
    • 以下场景会自动触发 RDB 持久化

      • 在 Redis 配置文件(通常是 redis.conf)中,可以通过save <seconds> <changes>指令配置自动触发 RDB 持久化的条件。这个指令可以设置多次,每个设置定义了一个时间间隔(秒)和该时间内发生的变更次数阈值
      save 900 1
      save 300 10
      save 60 10000
      
      如果至少有 1 个键被修改,900 秒后自动触发一次 RDB 持久化。
      如果至少有 10 个键被修改,300 秒后自动触发一次 RDB 持久化。
      如果至少有 10000 个键被修改,60 秒后自动触发一次 RDB 持久化。
      
      • Redis 服务器通过 SHUTDOWN 命令正常关闭时,如果没有禁用 RDB 持久化,Redis 会自动执行一次 RDB 持久化,以确保数据在下次启动时能够恢复。
      • 在 Redis 复制场景中,当一个 Redis 实例被配置为从节点并且与主节点建立连接时,它可能会根据配置接收主节点的 RDB 文件来初始化数据集。这个过程中,主节点会在后台自动触发 RDB 持久化,然后将生成的 RDB 文件发送给从节点
  4. AOF

    • AOF 持久化通过记录每个写操作命令并将其追加到 AOF 文件中来工作,恢复时通过重新执行这些命令来重建数据集。

    • 简单工作流程:

      • 命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load)

        三分恶面渣逆袭:AOF工作流程

    • 详细工作流程:

      1)当 AOF 持久化功能被启用时,Redis 服务器会将接收到的所有写命令(比如 SET, LPUSH, SADD 等修改数据的命令)追加到 AOF 缓冲区(buffer)的末尾

      2)为了将缓冲区中的命令持久化到磁盘中的 AOF 文件,Redis 提供了几种不同的同步策略:

      • always:每次写命令都会同步到 AOF 文件,这提供了最高的数据安全性,但可能因为磁盘 I/O 的延迟而影响性能。
      • everysec(默认):每秒同步一次,这是一种折衷方案,提供了较好的性能和数据安全性。
      • no:不主动进行同步,交由操作系统决定何时将缓冲区数据写入磁盘,这种方式性能最好,但在系统崩溃时可能会丢失最近一秒的数据。

      3)随着操作的不断执行,AOF 文件会不断增长,为了减小 AOF 文件大小,Redis 可以重写 AOF 文件

      • 重写过程不会解析原始的 AOF 文件,而是将当前内存中的数据库状态转换为一系列写命令,然后保存到一个新的 AOF 文件中。
      • AOF 重写操作由 BGREWRITEAOF 命令触发,它会创建一个子进程来执行重写操作,因此不会阻塞主进程。
      • 重写过程中,新的写命令会继续追加到旧的 AOF 文件中,同时也会被记录到一个缓冲区中。一旦重写完成,Redis 会将这个缓冲区中的命令追加到新的 AOF 文件中,然后切换到新的 AOF 文件上,以确保数据的完整性。

      4)当 Redis 服务器启动时,如果配置为使用 AOF 持久化方式,它会读取 AOF 文件中的所有命令并重新执行它们,以恢复数据库的状态。

8.RDB和AOF的优缺点?

  • RDB

    • 优点:
      1. 适用于全量备份:只有一个紧凑的二进制文件 dump.rdb,非常适合备份、全量复制的场景。
      2. 恢复速度快,RDB 恢复数据的速度远远快于 AOF 的方式
      3. 容灾性好,可以把 RDB 文件拷贝道远程机器或者文件系统张,用于容灾恢复。
    • 缺点:
      1. 实时性低,RDB 是间隔一段时间进行持久化,没法做到实时持久化/秒级持久化。如果在这一间隔事件发生故障,数据会丢失。
      2. 存在兼容问题,Redis 演进过程存在多个格式的 RDB 版本,存在老版本 Redis 无法兼容新版本 RDB 的问题。
  • AOF

    • 优点:
      1. 实时性好,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
      2. 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
    • 缺点:
      1. AOF 文件比 RDB 文件大,且 恢复速度慢
      2. 数据集大 的时候,比 RDB 启动效率低

9.RDB和AOF如何选择

  1. 一般来说, 如果想达到足以媲美数据库的 数据安全性,应该 同时使用两种持久化功能。在这种情况下,当 Redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。
  2. 如果 可以接受数分钟以内的数据丢失,那么可以 只使用 RDB 持久化
  3. 有很多用户都只使用 AOF 持久化,但并不推荐这种方式,因为定时生成 RDB 快照(snapshot)非常便于进行数据备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用 RDB 还可以避免 AOF 程序的 bug。
  4. 如果只需要数据在服务器运行的时候存在,也可以不使用任何持久化方式

10.Redis的数据恢复?

  1. 当 Redis 发生了故障,可以从 RDB 或者 AOF 中恢复数据。
  2. 恢复的过程也很简单,把 RDB 或者 AOF 文件拷贝到 Redis 的数据目录下,如果使用 AOF 恢复,配置文件开启 AOF,然后启动 redis-server 即可。

Redis启动加载数据

Redis 启动时加载数据的流程:

  1. AOF 持久化开启且存在 AOF 文件时,优先加载 AOF 文件
  2. AOF 关闭或者 AOF 文件不存在时,加载 RDB 文件。
  3. 加载 AOF/RDB 文件成功后,Redis 启动成功。
  4. AOF/RDB 文件存在错误时,Redis 启动失败并打印错误信息

11.Redis4.0的混合持久化了解吗?

  1. 重启 Redis 时,我们很少使用 RDB 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。
  2. Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是 自持久化开始到持久化结束 的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小:
  3. 于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

image-20240328091333669

底层数据结构

12.说说Redis的底层数据结构?

  1. Redis 的底层数据结构有
    • 动态字符串(sds)
    • 字典(ht)
    • 双向链表链表(list)
    • 整数集合(intset)
    • 压缩列表(ziplist)
    • 跳跃表(skiplist)

image-20240328091808629

13.跳跃表是如何实现的?原理?

  1. 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。

image-20240328092218623

  1. ZSet数据结构底层使用跳跃表的原因?

    • 首先,因为 zset 要支持随机的插入和删除,所以它 不宜使用数组来实现,关于排序问题,我们也很容易就想到 红黑树/ 平衡树 这样的树形结构,为什么 Redis 不使用这样一些结构呢?

      • 性能考虑 在高并发的情况下,树形结构需要执行一些类似于 rebalance 这样的可能涉及整棵树的操作,相对来说跳跃表的变化只涉及局部;

      • 实现考虑 在复杂度与红黑树相同的情况下,跳跃表实现起来更简单,看起来也更加直观;

    • 基于以上的一些考虑,Redis 基于 William Pugh 的论文做出一些改进后采用了 跳跃表 这样的结构。

  2. 跳跃表是怎么实现的?

    • Redis的跳跃表由zskiplistNode和skiplist两个结构定义

    • zskiplist结构保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等等

    • zskiplistNode结构用于表示跳跃表节点

    • 跳跃表的节点元素类型:

      • 层(level):跳跃表节点的 level 数组可以包含多个元素,每个元素都包含一个指向其它节点的指针,程序可以通过这些层来加快访问其它节点的速度,一般来说,层的数量越多,访问其它节点的速度就越快

      • 前进指针:每个层都有一个指向表尾的前进指针(level[i].forward 属性),用于从表头向表尾方向访问节点。

        我们看一下跳跃表从表头到表尾,遍历所有节点的路径:

        通过前进指针遍历

      • 跨度:层的跨度用于记录两个节点之间的距离。跨度是用来计算排位(rank)的:在查找某个节点的过程中,将沿途访问过的所有层的跨度累计起来,得到的结果就是目标节点在跳跃表中的排位。(例如,箭头连线上的数字就是跨度,即分值

        Redis跳跃表

在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看