Redis为什么在RDB快照时会通过子进程的方式进行实现?
rdbSaveBackGround
函数:将内存中的数据以 RDB 格式保存到磁盘中
1 | int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) { |
函数中在快照前会先通过redisFork()
创建子进程
为什么
fork
之后的子进程能够获取父进程内存中的数据?fork
函数是否会带来额外的性能开销,这些开销我们怎么样才可以避免?
设计
通过
fork
生成的父子进程会共享包括内存空间在内的资源;fork
函数并不会带来明显的性能开销,尤其是对内存进行大量的拷贝,它能通过写时拷贝将拷贝内存这一工作推迟到真正需要的时候。
Redis 实现后台快照的方式非常巧妙,通过操作系统提供的 fork
和写时拷贝的特性轻而易举的就实现了这个功能,从这里我们就能看出作者对于操作系统知识的掌握还是非常扎实的,大多人在面对类似的场景时,想到的方法可能就是手动实现类似写时拷贝的特性,然而这不仅增加了工作量,还增加了程序出现问题的可能性。
到这里,我们简单总结一下 Redis 为什么在使用 RDB快照时会通过子进程的方式进行实现:
通过
fork
创建的子进程能够获得和父进程完全相同的内存空间,父进程对内存的修改对于子进程是不可见的,两者不会相互影响;通过
fork
创建子进程时不会立刻触发大量内存的拷贝,内存在被修改时会以页为单位进行拷贝,这也就避免了大量拷贝内存而带来的性能问题;
上述两个原因中,一个为子进程访问父进程提供了支撑,另一个为减少额外开销做了支持,这两者缺一不可,共同成为了 Redis 使用子进程实现快照持久化的原因。到最后,我们还是来看一些比较开放的相关问题,有兴趣的读者可以仔细思考一下下面的问题:
Nginx 的主进程会在运行时
fork
一组子进程,这些子进程可以分别处理请求,还有哪些服务会使用这一特性?写时拷贝其实是一个比较常见的机制,在 Redis 之外还有哪里会用到它?