文章目录
- 基础
- 1.什么是Redis?
- 2.Redis可以用来干什么?
- 3.Redis的五种基本数据结构?
- 4.Redis为什么这么快?
- 5.什么是I/O多路复用?
- 6.Redis6.0为什么使用了多线程?
- 持久化
- 7.Redis的持久化方式?区别?
- 8.RDB和AOF的优缺点?
- 9.RDB和AOF如何选择
- 10.Redis的数据恢复?
- 11.Redis4.0的混合持久化了解吗?
- 底层数据结构
- 12.说说Redis的底层数据结构?
- 13.跳跃表是如何实现的?原理?
基础
1.什么是Redis?
- Redis即 Remote Dictionary Service 三个单词中加粗字母的组合,是一种基于键值对(key-value)的 NoSQL 数据库。
- value值支持多种数据结构:string(字符串)、hash(哈希)、 list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、HyperLogLogopen in new window(基数估算)、GEO(地理信息定位)
- Redis 的所有数据都存放在内存当中,所以它的读写性能非常出色
- Redis 还可以将内存数据持久化到硬盘上,这样在发生类似断电或者机器故障的时候,内存中的数据并不会“丢失”
2.Redis可以用来干什么?
- 热点数据缓存
- 计数器(记录浏览量、点赞收藏量)
- 分布式锁(SETNX命令+过期时间+Lua脚本)
- 排行榜(有序集合)
- 社交网络(共同好友/喜好)
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 的⼏⼗倍。速度快的原因主要有几点:
- 基于内存的数据存储,Redis 将数据存储在内存当中,使得数据的读写操作避开了磁盘 I/O。而内存的访问速度远超硬盘,这是 Redis 读写速度快的根本原因。
- 单线程模型,Redis 使用单线程模型来处理客户端的请求,这意味着在任何时刻只有一个命令在执行。这样就避免了线程切换和锁竞争带来的消耗。
- 高效的数据结构,Redis 提供了多种高效的数据结构,如字符串(String)、列表(List)、集合(Set)、有序集合(Sorted Set)等,这些数据结构经过了高度优化,能够支持快速的数据操作。
- 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为什么使用了多线程?
- Redis6.0 的多线程是用多线程来处理数据的读写和协议解析,但是 Redis执行命令还是单线程的。
- 这样做的⽬的是因为 Redis 的性能瓶颈在于⽹络 IO ⽽⾮ CPU,使⽤多线程能提升 IO 读写的效率,从⽽整体提⾼ Redis 的性能。
持久化
7.Redis的持久化方式?区别?
-
Redis 支持两种主要的持久化方式:RDB(Redis DataBase)持久化和 AOF(Append Only File)持久化。
-
这两种方式可以单独使用,也可以同时使用。
-
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 文件发送给从节点
- 在 Redis 配置文件(通常是 redis.conf)中,可以通过
-
-
AOF
-
AOF 持久化通过记录每个写操作命令并将其追加到 AOF 文件中来工作,恢复时通过重新执行这些命令来重建数据集。
-
简单工作流程:
-
命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load)
-
-
详细工作流程:
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
- 优点:
- 适用于全量备份:只有一个紧凑的二进制文件
dump.rdb
,非常适合备份、全量复制的场景。 - 恢复速度快,RDB 恢复数据的速度远远快于 AOF 的方式
- 容灾性好,可以把 RDB 文件拷贝道远程机器或者文件系统张,用于容灾恢复。
- 适用于全量备份:只有一个紧凑的二进制文件
- 缺点:
- 实时性低,RDB 是间隔一段时间进行持久化,没法做到实时持久化/秒级持久化。如果在这一间隔事件发生故障,数据会丢失。
- 存在兼容问题,Redis 演进过程存在多个格式的 RDB 版本,存在老版本 Redis 无法兼容新版本 RDB 的问题。
- 优点:
-
AOF
- 优点:
- 实时性好,aof 持久化可以配置
appendfsync
属性,有always
,每进行一次命令操作就记录到 aof 文件中一次。 - 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
- 实时性好,aof 持久化可以配置
- 缺点:
- AOF 文件比 RDB 文件大,且 恢复速度慢。
- 数据集大 的时候,比 RDB 启动效率低。
- 优点:
9.RDB和AOF如何选择
- 一般来说, 如果想达到足以媲美数据库的 数据安全性,应该 同时使用两种持久化功能。在这种情况下,当 Redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。
- 如果 可以接受数分钟以内的数据丢失,那么可以 只使用 RDB 持久化。
- 有很多用户都只使用 AOF 持久化,但并不推荐这种方式,因为定时生成 RDB 快照(snapshot)非常便于进行数据备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用 RDB 还可以避免 AOF 程序的 bug。
- 如果只需要数据在服务器运行的时候存在,也可以不使用任何持久化方式
10.Redis的数据恢复?
- 当 Redis 发生了故障,可以从 RDB 或者 AOF 中恢复数据。
- 恢复的过程也很简单,把 RDB 或者 AOF 文件拷贝到 Redis 的数据目录下,如果使用 AOF 恢复,配置文件开启 AOF,然后启动 redis-server 即可。
Redis 启动时加载数据的流程:
- AOF 持久化开启且存在 AOF 文件时,优先加载 AOF 文件。
- AOF 关闭或者 AOF 文件不存在时,加载 RDB 文件。
- 加载 AOF/RDB 文件成功后,Redis 启动成功。
- AOF/RDB 文件存在错误时,Redis 启动失败并打印错误信息
11.Redis4.0的混合持久化了解吗?
- 重启 Redis 时,我们很少使用
RDB
来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对RDB
来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。 - Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将
rdb
文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是 自持久化开始到持久化结束 的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小: - 于是在 Redis 重启的时候,可以先加载
rdb
的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
底层数据结构
12.说说Redis的底层数据结构?
- Redis 的底层数据结构有
- 动态字符串(sds)
- 字典(ht)
- 双向链表链表(list)
- 整数集合(intset)
- 压缩列表(ziplist)
- 跳跃表(skiplist)
13.跳跃表是如何实现的?原理?
- 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。
-
ZSet数据结构底层使用跳跃表的原因?
-
首先,因为 zset 要支持随机的插入和删除,所以它 不宜使用数组来实现,关于排序问题,我们也很容易就想到 红黑树/ 平衡树 这样的树形结构,为什么 Redis 不使用这样一些结构呢?
-
性能考虑: 在高并发的情况下,树形结构需要执行一些类似于 rebalance 这样的可能涉及整棵树的操作,相对来说跳跃表的变化只涉及局部;
-
实现考虑: 在复杂度与红黑树相同的情况下,跳跃表实现起来更简单,看起来也更加直观;
-
-
基于以上的一些考虑,Redis 基于 William Pugh 的论文做出一些改进后采用了 跳跃表 这样的结构。
-
-
跳跃表是怎么实现的?
-
Redis的跳跃表由zskiplistNode和skiplist两个结构定义
-
zskiplist结构保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等等
-
zskiplistNode结构用于表示跳跃表节点
-
跳跃表的节点元素类型:
-
层(level):跳跃表节点的 level 数组可以包含多个元素,每个元素都包含一个指向其它节点的指针,程序可以通过这些层来加快访问其它节点的速度,一般来说,层的数量越多,访问其它节点的速度就越快。
-
前进指针:每个层都有一个指向表尾的前进指针(level[i].forward 属性),用于从表头向表尾方向访问节点。
我们看一下跳跃表从表头到表尾,遍历所有节点的路径:
-
跨度:层的跨度用于记录两个节点之间的距离。跨度是用来计算排位(rank)的:在查找某个节点的过程中,将沿途访问过的所有层的跨度累计起来,得到的结果就是目标节点在跳跃表中的排位。(例如,箭头连线上的数字就是跨度,即分值)
-
-