Redis的RDB是怎么实现的?
作者:程序员马丁
在线博客:https://open8gu.com
大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。
答题思路
回答话术
当通过 BGSAVE
指令生成 RDB 的时候,Redis 会 fork
出一个子进程,它会基于写时复制机制,在不阻塞主线程的情况下,将此刻的数据库全量数据保存为二进制快照。
具体的来说,在最开始的时候 fork
子进程的时候,操作系统会为其拷贝父进程的内存页,但是此时两者都指向同一块物理内存。当主进程发生写操作时,会真正的将父进程的数据拷贝到独立的物理内存中。此时父子进程的物理内存彼此独立,互不干涉,父进程继续处理增量数据,而子进程则根据拷贝出来的旧数据生成 RDB 快照。
相对 AOF,RDB 生成的二进制文件更小,数据恢复起来更快,并且整个流程中完全不会阻塞主进程,但是对应的,由于写时复制,在最坏的情况下可能会占用双倍内存,并且无法保存增量数据。
问题详解
1. 如何生成 RDB 文件
我们可以通过 SAVE
命令在主进程中阻塞的生成 RDB 文件,或者通过 BGSAVE
命令指定在子进程中生成 RDB 文件。
此外,也可以在数据库中通过 save
选项,指定当在一定的时间范围内执行了多少次修改时生成 RDB 文件。比如 save 120 10000
表示当在 120 秒内发生了 10000 次修改时,就通过 BGSAVE
在后台生成一分 RDB 文件。
关于数据是如何保存在 RDB 文件中的,请参考文章:Redis RDB sourl.cn/mz7P6X
2. 实现原理
当 Redis 在 fork 出一个子进程去生成 RDB 文件时,主进程依然还在不同的接受指令并操作数据。与 AOF 不同,由于 RDB 是快照,因此实际上它不会同步增量数据,不过也不会影响父进程的操作。Redis 通过写时复制机制实现这样的效果。
简单的来说,当 fork 子进程后,虽然子进程会从父进程中拷贝内存页,但是实际上的内存页依然还是指向了原本的物理内存。而当父进程修改了内存后,才会真正在物理内存中复制一遍父进程的数据,并让子进程的内存页指向这块物理内存,这就是写时复制。
根据这个原理,当子进程在生成 RDB 文件时,若父进程发生写操作,由于写时复制,子进程会得到一份父进程内存页的拷贝。此时子进程读取的是此时父进程内存数据的拷贝,而父进程继续操作他原本的那份内存,两者互不干涉。
我们都知道,在 Linux 系统中内存页一页是 4KB,由于是以内存页为单位的复制,所以在这个过程中,只要不是所有内存页上的数据都发生了修改,那么生成 RDB 文件过程中占用的额外内存是比较低的。
3. 与 AOF 的区别
RDB 即 Redis Database,它本质上就是某个时刻的全量数据快照。它以二进制的方式保存了某个时刻 Redis 数据库中的全部数据,Redis 启动后只需要将其加载进内存即可恢复数据。
相比起 AOF,它的优点是:
- 文件更小:由于 RDB 里面存放的是非常紧凑的二进制数据,因此相比起 AOF 文件,它占用的空间更小,因此也更适合用来频繁的进行全量备份。
- 加载更快:Redis 只需要将 RDB 加载进内存即可恢复数据,相比起还需要重放指令的 AOF,它要快得多。
- 不阻塞主进程:生成 RDB 的过程可以完全在子进程中完成,因而不需要在主线程进行任何 IO 操作。
对应的,它的缺点是:
- RDB 需要 fork 出子进程去完成这个任务,当比较频繁的生成 RDB 文件时,会对 CPU 带来比较大的压力;
- 无法保存增量数据。