Redis的快照为什么不会阻塞其他请求?
|
最关键的点在于父子进程的内存在 fork 时是完全相同的,在 fork 之后进行写入和修改也不会相互影响,这其实就完美的解决了快照这个场景的问题 —— 只需要某个时间点下内存中的数据,而父进程可以继续对自己的内存进行修改,这既不会被阻塞,也不会影响生成的快照。 写时拷贝 既然父进程和子进程拥有完全相同的内存空间并且两者对内存的写入都不会相互影响,那么是否意味着子进程在 fork 时需要对父进程的内存进行全量的拷贝呢?假设子进程需要对父进程的内存进行拷贝,这对于 Redis 服务来说基本都是灾难性的,尤其是在以下的两个场景中: 内存中存储大量的数据,fork 时拷贝内存空间会消耗大量的时间和资源,会导致程序一段时间的不可用; Redis 占用了 10G 的内存,而物理机或者虚拟机的资源上限只有 16G,在这时我们就无法对 Redis 中的数据进行持久化,也就是说 Redis 对机器上内存资源的最大利用率不能超过 50%; 如果无法解决上面的两个问题,使用 fork 来生成内存镜像的方式也无法真正落地,不是一个工程中真正可以使用的方法。 就算脱离了 Redis 的场景,fork 时全量拷贝内存也是难以接受的,假设我们需要在命令行中执行一个命令,我们需要先通过 fork 创建一个新的进程再通过 exec 来执行程序,fork 拷贝的大量内存空间对于子进程来说可能完全没有任何作用的,但是却引入了巨大的额外开销。 写时拷贝(Copy-on-Write)的出现就是为了解决这一问题,就像我们在这一节开头介绍的,写时拷贝的主要作用就是将拷贝推迟到写操作真正发生时,这也就避免了大量无意义的拷贝操作。在一些早期的 *nix 系统上,系统调用 fork 确实会立刻对父进程的内存空间进行复制,但是在今天的多数系统中,fork 并不会立刻触发这一过程:
在 fork 函数调用时,父进程和子进程会被 Kernel 分配到不同的虚拟内存空间中,所以在两个进程看来它们访问的是不同的内存: 在真正访问虚拟内存空间时,Kernel 会将虚拟内存映射到物理内存上,所以父子进程共享了物理上的内存空间; 当父进程或者子进程对共享的内存进行修改时,共享的内存才会以页为单位进行拷贝,父进程会保留原有的物理空间,而子进程会使用拷贝后的新物理空间; 在 Redis 服务中,子进程只会读取共享内存中的数据,它并不会执行任何写操作,只有父进程会在写入时才会触发这一机制,而对于大多数的 Redis 服务或者数据库,写请求往往都是远小于读请求的,所以使用 fork 加上写时拷贝这一机制能够带来非常好的性能,也让 BGSAVE 这一操作的实现变得非常简单。 总结 Redis 实现后台快照的方式非常巧妙,通过操作系统提供的 fork 和写时拷贝的特性轻而易举的就实现了这个功能,从这里我们就能看出作者对于操作系统知识的掌握还是非常扎实的,大多人在面对类似的场景时,想到的方法可能就是手动实现类似『写时拷贝』的特性,然而这不仅增加了工作量,还增加了程序出现问题的可能性。 到这里,我们简单总结一下 Redis 为什么在使用 RDB 进行快照时会通过子进程的方式进行实现: 通过 fork 创建的子进程能够获得和父进程完全相同的内存空间,父进程对内存的修改对于子进程是不可见的,两者不会相互影响; 通过 fork 创建子进程时不会立刻触发大量内存的拷贝,内存在被修改时会以页为单位进行拷贝,这也就避免了大量拷贝内存而带来的性能问题; (编辑:应用网_扬州站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


