子进程
在计算机编程领域,尤其是 Unix 和类 Unix 系统中,fork
都是一个进程用于创建自己拷贝的操作,它往往都是被操作系统内核实现的系统调用,也是操作系统在 *nix 系统中创建新进程的主要方法。
当程序调用了 fork
方法之后,我们就可以通过 fork
的返回值确定父子进程,以此来执行不同的操作:
fork
函数返回 0 时,意味着当前进程是子进程;fork
函数返回非 0 时,意味着当前进程是父进程,返回值是子进程的pid
;
C
在 fork
的 手册 中,我们会发现调用 fork
后的父子进程会运行在不同的内存空间中,当 fork
发生时两者的内存空间有着完全相同的内容,对内存的写入和修改、文件的映射都是独立的,两个进程不会相互影响。
The child process and the parent process run in separate memory spaces. At the time of fork() both memory spaces have the same content. Memory writes, file mappings (mmap(2)), and unmappings (munmap(2)) performed by one of the processes do not affect other.
除此之外,子进程几乎是父进程的完整副本(Exact duplicate),然而这两个进程在以下的一些方面会有较小的区别:
子进程用于独立且唯一的进程 ID;
子进程的父进程 ID 与父进程 ID 完全相同;
子进程不会继承父进程的内存锁;
子进程会重新设置进程资源利用率和 CPU 计时器;
…
最关键的点在于父子进程的内存在 fork
时是完全相同的,在 fork
之后进行写入和修改也不会相互影响,这其实就完美的解决了快照这个场景的问题 —— 只需要某个时间点下内存中的数据,而父进程可以继续对自己的内存进行修改,这既不会被阻塞,也不会影响生成的快照。
写时拷贝
既然父进程和子进程拥有完全相同的内存空间并且两者对内存的写入都不会相互影响,那么是否意味着子进程在 fork
时需要对父进程的内存进行全量的拷贝呢?假设子进程需要对父进程的内存进行拷贝,这对于 Redis 服务来说基本都是灾难性的,尤其是在以下的两个场景中:
内存中存储大量的数据,
fork
时拷贝内存空间会消耗大量的时间和资源,会导致程序一段时间的不可用;Redis 占用了 10G 的内存,而物理机或者虚拟机的资源上限只有 16G,在这时我们就无法对 Redis 中的数据进行持久化,也就是说 Redis 对机器上内存资源的最大利用率不能超过 50%;
如果无法解决上面的两个问题,使用 fork
来生成内存镜像的方式也无法真正落地,不是一个工程中真正可以使用的方法。
就算脱离了 Redis 的场景,
fork
时全量拷贝内存也是难以接受的,假设我们需要在命令行中执行一个命令,我们需要先通过fork
创建一个新的进程再通过exec
来执行程序,fork
拷贝的大量内存空间对于子进程来说可能完全没有任何作用的,但是却引入了巨大的额外开销。
本文转载自 Draveness 技术博客。
原文链接:https://draveness.me/whys-the-design-redis-bgsave-fork
评论