Redis的写时复制(Copy On Write),你真的了解么?
首先,我们都知道 Redis 可以做一个纯内存的键值存储系统,但是我们也会用到它的持久化功能,RDB 和 AOF 就是 Redis 为我们提供的两种持久化工具:RDB做镜像全量持久化,AOF做增量持久化
RDB:指定的时间间隔内将内存的数据集快照写入磁盘
Redis 的配置文件可以对快照的间隔进行设置,Redis 客户端还同时提供SAVE 和 BGSAVE两个命令来生成 RDB 存储文件
1:SAVE 命令在执行时会直接阻塞当前的线程
2:BGSAVE 命令时,Redis 会立刻 fork 出一个子进程,子进程做数据备份操作,主进程继续对外提供服务,所有Redis服务不会阻塞
Redis fork的实现?
Redis中执行BGSAVE命令生成RDB文件时,本质就是调用Linux中的fork()命令,Linux下的fork()系统调用实现了copy-on-write写时复制
fork()和exec()函数了解
exec()并不是一个特定的函数, 它是一组函数的统称, 它包括了execl()、execlp()、execv()、execle()、execve()、execvp()
fork是类Unix操作系统上创建进程的主要方法。fork用于创建子进程(等同于当前进程的副本)
Copy On Write 机制:
在fork出子进程后,与父进程共享内存空间,两者只是虚拟空间不同,但是其对应的物理空间是同一个【减少对物理内存的消耗】; 只有在父进程发生写操作修改内存数据时,才会真正去分配内存空间,并复制内存数据,而且也只是复制被修改的内存页中的数据,并不是全部内存数据
传统的普通进程复制,会直接将父进程的数据拷贝到子进程中,拷贝完成后,父进程和子进程之间的数据段和堆栈是相互独立的
linux 内存页大小为 4KB
Linux中CopyOnWrite实现原理
fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中的某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
CopyOnWrite的好处:
1:减少分配和复制资源时带来的瞬时延迟;
2:减少不必要的资源分配;
CopyOnWrite的缺点:
1:如果父子进程都需要进行大量的写操作,会产生大量的分页错误(页异常中断page-fault)
写时复制,多更改条件下,内存被大量复制,重写完成后,数据如何释放?
本质来说,这是一个进程的内存资源管理问题:
内核提供的写时复制机制避免了大量内存拷贝以及占用过多内存
子进程fork操作只是拷贝了父进程的页表数据,当fork完成后,对于一个父进程引用的内存页,其引用值从1变成2.
当父进程进行写操作时,会申请一份新的内存页,并从原内存页copy所有数据,之后父进程就在新的内存页上进行操作。而原内存页就变成了子进程的专属内存页,引用值变成了1
子进程重写完成并退出后,内核会对资源进行回收,包括占用的内存资源
回收:对于页表来说,是进程专属,直接清理即可;而关联的内存页会将其引用值减1,如果引用值变成了0,该内存页就会被回收
也就是复制之前的页(原内存页),变成了子进程的专属页,当子进程结束后就会被回收掉