redis持久化原理相关面试题剖析
一、Redis 持久化的机制是什么?
Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次
Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置
既然redis的数据可以保存在磁盘上,那么这个流程是什么样的呢?
- (1)客户端向服务端发送写操作(数据在客户端的内存中)。
- (2)数据库服务端接收到写请求的数据(数据在服务端的内存中)。
- (3)服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)。
- (4)操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)。
- (5)磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)。
前三步是由redis完成的,后两步是由OS完成的
这5个过程是在理想条件下一个正常的保存流程,但是在大多数情况下,我们的机器等等都会有各种各样的故障,这里划分了两种情况
(1)Redis数据库发生故障,只要在上面的第三步执行完毕,那么就可以持久化保存,剩下的两步由操作系统替我们完成。
(2)操作系统发生故障,必须上面5步都完成才可以。
为应对以上5步操作,redis提供了两种不同的持久化方式:RDB(Redis DataBase)和AOF(Append Only File)
RDB和AOF都可以在redis.conf进行相关配置
RDB是什么
在指定的时间间隔能对你的数据进行快照存储。是将当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。
AOF是什么
AOF通过记录每次对服务器写的操作(命令),当服务器重启的时候会重新执行这些命令来恢复原始的数据。
特点:
- 1. 以日志的形式来记录用户请求的写操作,读操作不会记录,因为写操作才会存储
- 2. 文件以追加的形式而不是修改的形式
- 3. redis的aof恢复其实就是通过一个伪客户端把追加的文件从开始到结尾读取 执行 写操作
二、redis持久化的RDB机制了解吗,rdb bgsave的时候存储的数据是拷贝给子进程的吗?
主要有两个命令,save命令和bgsave命令都可以生成RDB文件
在接收到save命令后,redis服务器的主进程就会读取内存和生成RDB文件,这两件事合为RDB操作,由主进程实现,所以主进程干这个事去了,就无法处理新的redis客户端的请求了,就会阻塞住。
bgsave:bgsave 通过fork一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配,fork子进程负责持久化过程,阻塞只会发生在fork子进程的时候。
bgsave在进行生成快照时,肯定要访问当前redis内存中存储的数据,那既然是快照,是否会复制当前这一时刻内存中所有的数据呢?
答案是:不会直接拷贝数据(内存使用率超过50%,直接拷贝不就gg了!!)
进行fork操作后,会利用操作系统的“写时复制”(Copy-On-Write,COW)机制:
(1)、执行bgsave命令的时候,会通过fork()创建子进程,此时子进程和父进程是共享同一片内存数据的,因为创建子进程的时候,会复制父进程的页表,但是页表指向的物理内存还是同一个
这时候,共享同一片物理内存,然后直接读取,生成临时rdb文件,生成完毕,再覆盖之前的rdb文件,就结束bgsave的流程。但是你会有一个疑问, 都是共享一片内存,那父进程这时候收到别的redis客户端请求需要写数据,那么不同样会操作同一片内存空间吗,就会造成错误。
(2)、这时就有了写时复制,只有在修改内存数据的情况时,物理内存才会被复制一份。然后各个进程各自读各自的内存,互不影响
写时复制COW
fork一个子进程,只有在父进程发生写操作修改内存数据时,才会真正去分配内存空间,并复制内存数据,而且也只是复制被修改的内存页中的数据,并不是全部内存数据
fork采用操作系统提供的写实复制(Copy On Write)机制,就是为了避 免一次性拷贝大量内存数据给子进程造成的长时间阻塞问题,但fork子进程 需要拷贝进程必要的数据结构,其中有一项就是拷贝内存页表(虚拟内存 和物理内存的映射索引表),这个拷贝过程会消耗大量CPU资源,拷贝完 成之前整个进程是会阻塞的,阻塞时间取决于整个实例的内存大小,实例 越大,内存页表越大,fork阻塞时间越久。拷贝内存页表完成后,子进程与 父进程指向相同的内存地址空间,也就是说此时虽然产生了子进程,但是 并没有申请与父进程相同的内存大小。那什么时候父子进程才会真正内存 分离呢?“写时复制”顾名思义,就是在写发生时,才真正拷贝内存真正的数据
三、聊聊redis持久化的AOF机制,AOF文件很大怎么办?
AOF 日志是写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存,然后才记录日志,这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到日志中,否则,系统就会直接向客户端报错。
所以,Redis使用写后日志这一方式的一大好处是,可以避免出现记录错误命令的情况;另外还有个好处是:不会阻塞当前的写操作(但可能阻塞下一个操作)。
我们看到,当命令产生的时候,我们先将命令写到AOF缓冲区,这是一块BUFFER,速度很快,而最终由操作系统以一定频率将缓冲区同步落盘到最后的AOF文件
AOF 机制给我们提供了三个选择,也就是 AOF 配置项 appendfsync的三个可选值。
- Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
- Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
- No,操作系统控制的写回:每个写命令执行完,只是先把日志写到AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
AOF文件过大怎么办
我们知道,AOF文件存储了所有命令,这个文件很短时间就会增加到很大,随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
AOF 重写机制就是在重写时,Redis 根据数据库的现状创建一个新的AOF 文件,也就是说,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入。比如说,当读取了键值对“testkey”: “testvalue”之后,重写机制会记录 set testkey testvalue 这条命令。这样,当需要恢复时,可以重新执行该命令,实现“testkey”: “testvalue”的写入
AOF 文件是以追加的方式,逐一记录接收到的写命令的。当一个键值对被多条写命令反复修改时,AOF 文件会记录相应的多条命令。但是,在重写的时候,是根据这个键值对当前的最新状态,为它生成对应的写入命令。这样一来,一个键值对在重写日志中只用一条命令就行了,而且,在日志恢复时,只用执行这条命令,就可以直接完成这个键值对的写入了。
重写原理:
AOF 文件重写并不需要对现有的 AOF 文件进行任何读取、分析或者写 入操作,而是通过读取服务器当前的数据库状态来实现的。首先从数据库 中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键 值对的多条命令,这就是 AOF 重写功能的实现原理。
AOF工作流程
四、请你谈谈Redis两种持久化机制的缺点,最佳实践是什么?
RDB
- 数据丢失风险:RDB 是周期性持久化,假设时间间隔为5s,第1s进行更新,第4s发送宕机,本来该第5s发送下一次快照更新,但是由于宕机,第1s和第4s之间的更新操作就丢失了,也就是说在上次快照和故障之间的数据会丢失。对于高可用需求的场景,AOF可能更合适。
- 资源开销大:由于RDB是进行全量更新,所以时间间隔不能设置太短,否则导致频繁生成快照执行fork命令等等,RDB 持久化使用 fork 生成子进程,并且占用大量内存。对于大型数据集,频繁的 RDB 持久化可能对性能产生负面影响。
- 执行时间较长:RDB 的生成需要将整个内存快照写入磁盘,对于大量数据会消耗较长时间。
AOF
-
文件体积增长较快:
- 日志膨胀:AOF 记录的是每一个写命令,随着时间推移,AOF 文件会变得非常大。即使启用了重写功能(
auto-aof-rewrite
),仍然可能遇到文件体积迅速增长的问题。 - 重写开销:虽然 Redis 提供了 AOF 重写功能来压缩日志文件,但重写过程本身也是一个耗时的操作,并且需要额外的磁盘空间。
- 日志膨胀:AOF 记录的是每一个写命令,随着时间推移,AOF 文件会变得非常大。即使启用了重写功能(
-
恢复速度较慢:
- 启动时间长:由于 AOF 文件记录了大量的命令,Redis 在重启时需要重新执行这些命令来恢复数据,这比直接加载 RDB 文件要慢得多,特别是在 AOF 文件非常大的情况下。
- 顺序执行:AOF 文件中的命令是按顺序执行的,因此恢复过程中任何一条命令的错误都可能导致整个恢复过程失败。
-
性能影响:
- 同步延迟:AOF 有三种同步策略:
no
、everysec
和always
。其中everysec
是最常见的选择,但它仍然可能引入一定的延迟,特别是在高并发写入场景下。 - 写操作开销:每次写操作都需要追加到 AOF 文件中,这对写密集型应用来说会带来额外的 I/O 开销。
- 同步延迟:AOF 有三种同步策略:
最佳实践
Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。