文章系转载,便于整理和分类,原文地址:https://zhuanlan.zhihu.com/p/339437815
摘要
问题概述: 1、RDB的过程中是否会停止对外提供服务? 2、RDB的过程中数据修改了,备份的是修改前的还是修改后的? 3、RDB时是不是先把内容中的所有KV复制一份,保证数据不会被修改?
问题解决:使用Copy On Write 写时复制
详细
在看Redis持久化方式中的RDB方式时,想到了几个问题:
1、 Redis是单线程的,那在RDB的过程中,是不是就没法对外提供服务了? Redis操作快的一个重要原因是Redis的数据是在内存中存储和操作的,持久化本身是磁盘的IO操作,IO操作又是特别耗时的,RDB备份的过程对Redis来说是挺漫长的,如果Redis没法对外提供服务的话,对Redis的影响是很大的吧; 2、知道备份时不会阻塞对外服务,那在数据备份的过程中,有新的数据变更的操作发生时,备份的是变更前的数据还是变更后的数据呢? 另一个角度:RDB快照的是精确的一个时刻的内存数据呢?还是一段时间内的内存数据? 另一个角度:RDB快照是精确的还是模糊的? 3、既然是数据备份,在开始备份的时候,是不是要把Redis的所有数据现在内存中拷贝一份呢?那样的话平时Redis服务器的内存利用率就不能大于50%了啊?
解答
1、RDB过程中会fork一个子进程,子进程做数据备份操作,主进程继续对外提供服务,所有Redis服务不会阻塞; 2、Copy On Write 机制,备份的是开始那个时刻内存中的数据; 3、Copy On Write 机制不需要把整个内存的数据都复制一份;
Copy On Write 机制
核心思路:fork一个子进程,只有在父进程发生写操作修改内存数据时,才会真正去分配内存空间,并复制内存数据,而且也只是复制被修改的内存页中的数据,并不是全部内存数据;
- Redis中执行BGSAVE命令生成RDB文件时,本质就是调用Linux中的fork()命令,Linux下的fork()系统调用实现了copy-on-write写时复制;
- fork()是类Unix操作系统上创建线程的主要方法,fork用于创建子进程(等同于当前进程的副本);
- 传统的普通进程复制,会直接将父进程的数据拷贝到子进程中,拷贝完成后,父进程和子进程之间的数据段和堆栈是相互独立的;
- copy-on-write技术,在fork出子进程后,与父进程共享内存空间,两者只是虚拟空间不同,但是其对应的物理空间是同一个;
Linux中CopyOnWrite实现原理
fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
CopyOnWrite的好处: 1、减少分配和复制资源时带来的瞬时延迟; 2、减少不必要的资源分配; CopyOnWrite的缺点: 1、如果父子进程都需要进行大量的写操作,会产生大量的分页错误(页异常中断page-fault);
Redis中的CopyOnWrite
- Redis在持久化时,如果是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis会fork出一个子进程来读取数据,从而写到磁盘中。
- 总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断page-fault),这样就得耗费不少性能在复制上。
- 而在rehash阶段上,写操作是无法避免的。所以Redis在fork出子进程之后,将负载因子阈值提高,尽量减少写操作,避免不必要的内存写入操作,最大限度地节约内存。