聊聊Redis中写时复制技术(COW)的应用
疑问
在看《Redis设计与实现》时,提到哈希表rehash时如果在进行BGSAVE或者BGWRITEAOF操作时,因为写时复制(CopyOnWrite)技术的优化,会暂时提高负载因子为5。
但是我的疑问并不是这个,而是在进行RDB操作时,假如数据量特别大,那么RDB操作岂不是会耗费大量的IO资源?
RDB流程及结论
带着疑问我翻看了一些博客和资料,首先我们来了解一些RDB的流程:
- 当redis需要做持久化时,会fork一个子进程
- redis根据pid判断下一步操作,如果是子进程,即pid==0时,则负责将内容写入临时RDB文件,完成之后通知父进程。
- 而父进程则继续处理命令并轮询等待子进程信号完成BGSAVE功能
pid = fork();
if(pid == 0){
// 写入临时RDB文件
rdbsave();
// 通知父进程
signal_parent();
} else if(pid > 0){
// 父进程继续处理请求,并轮询子进程信号
handle_request_and_wait_signal();
}
伪代码很简单,Redis每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。
如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能
因此redis会fork一个子进程出来干活,这也是为什么线上禁止使用SAVE命令的原因:可能会导致Redis阻塞!
RDB操作会耗费大量的IO资源这个疑问解决了,但是这个fork函数是干嘛的?写时复制技术又是什么时候用到的?
fork()与exec()函数
关于fork()与exec()函数的博客很多,我这里就不多做累赘,简单说说两者:
fork的作用是复制当前进程,生成子进程,子进程从fork的位置继续执行。就比如你fork()函数下面还有一个打印hello的语句,那么在执行fork()函数之后,父进程会打印一次,子进程同样也会打印一次,一共两次。
对比上面Redis的伪代码就能轻松理解RDB的流程了
但是,用fork创建子进程后执行的是和父进程相同的程序,子进程往往要调用一种exec函数以执行另一个程序。
调用exec并不创建新进程,所以调用exec前后该进程的id并未改变,这也是大部分博客将其称之为“替换”的原因。
调用成功就执行新代码,调用失败返回-1.
Redis与COW
redis fork出来的子进程会负责将内容写入临时RDB文件,因此大量的磁盘IO操作是必然的,此外,redis整体来说读操作比较多,但是写操作还是无法避免的,在RDB文件生成期间,复制带来的损耗是无法避免的
使用写时复制技术:
- 第一可以保证子进程快速构建,不耗费性能
- 第二可以保证数据完整性,不会因为断电后导致原有数据错乱
最后,Redis的哈希结构在rehash过程时,如果处于BGSAVE或者BGREWIRTEAOF阶段,负载因子会从默认的1提高到5(rehash也是渐进性的)的原因是:
虽然不rehash同样会产生页异常中断,但如果进行rehash操作的话,会将整个字典中的数据全部重新分配一次内存,导致产生大量复制