RDB内存快照
- Redis DataBase
- 内存快照:内存中的数据在某一个时刻的状态记录,某一时刻的状态以文件的形式写到磁盘上,也就是快照
- 恢复数据时可以把RDB文件直接读入内存,很快的完成恢复
对哪些数据做快照
- 全量数据
生成RDB命令
- save:在主线程中执行,会导致阻塞
- bgsave: 创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置
快照时数据能修改吗
- 可以修改
- Redis 就会借助操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作
- bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件
- 如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本(键值对 C’)。然后,主线程在这个数据副本上进行修改。同时,bgsave 子进程可以继续把原来的数据(键值对 C)写入 RDB 文件
写时复制 copy on write
fork函数
- fork是类Unix操作系统上创建进程的主要方法。fork用于创建子进程(等同于当前进程的副本)
- 新的进程要通过老的进程复制自身得到,这就是fork
- fork()会产生一个和父进程完全相同的子进程(除了pid)
exec函数
- 装载一个新的程序(可执行映像)覆盖当前进程内存空间中的映像,从而执行不同的任务
- exec系列函数在执行时会直接替换掉当前进程的地址空间
- 可以理解为exec执行时会清空进程的数据,重新设置
- 如果没有执行exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)
- 如果执行exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间
写时复制
- 按传统的做法,会直接将父进程的数据拷贝到子进程中,拷贝完之后,父进程和子进程之间的数据段和堆栈是相互独立的
- 但是往往子进程都会执行exec()来做自己想要实现的功能,exec会清空数据,导致很多时候复制给子进程的数据是无效的
- fork创建出的子进程,与父进程共享内存空间。也就是说,如果子进程不对内存空间进行写入操作的话,内存空间中的数据并不会复制给子进程,这样创建子进程的速度就很快
- 子进程第一时间exec一个新的可执行映像,那么也不会浪费时间和内存空间
- fork()之后,内核把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,内核创建当前内存页的副本用于修改
好处
- COW技术可减少分配和复制大量资源时带来的瞬间延时。
- COW技术可减少不必要的资源分配。比如fork进程时,并不是所有的页面都需要复制,父进程的代码段和只读数据段都不被允许修改,所以无需复制
快照频率?
- 频繁写入快照缺陷:
- 频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环
- fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了
AOF和RDB混合
- 内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作
- 快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销
结论
- 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择
- 如果允许分钟级别的数据丢失,可以只使用 RDB
- 如果只用 AOF,优先使用 everysec 的配置选项 在可靠性和性能之间比较平衡。