我是一个redis服务。我马上要挂电话了。
我已经运行它好几年了,内存中存储了很多键值对。
如果我挂了,我记忆中的所有数据都会消失。
我得想办法时不时把数据拷贝到硬盘里保存。
我称这个伟大的计划为持久的计划。
停下你正在做的事情。
首先,我想到的最简单的办法就是拒绝新的命令,开始把内存中的数据复制到硬盘上。
等到复制完成,再开始接受新的命令。
这样可以保证我复制的时候没有新的命令修改内存,保证了时效性。
简单来说,我保存了Redis在某个时刻的内存状态。
但是这样每次坚持下来都会屏蔽客户端命令,肯定会被骂。
不要停止你正在做的事情。
那就简单了,然后我不会停止我正在做的事情,我会接受命令并使之永久化,如下。
这大大提高了效率,并且持久性不再阻止客户端执行命令。
但是,你有没有注意到,在内存中,某个时刻的数据只有三种情况:
Flash 18低并发编程
●兄弟18低并发编程
弟18秋三连
而此时硬盘中的持久数据是:
山科十八丘三连
它不能代表任何时刻的内存数据。
那么这样的快照就失去了意义,即没有时效性的保证。
显然,这也不行。
先复制一份内存
但是我能做什么呢?
停下手头的工作可以保证时效性,但是却挡住了客户端。
不要停下手头的工作,虽然不堵客户端,但不能保证时效性。
真的很大。
抓了一会头皮,冷静下来开始分析。
时机一定要保证,否则快照就没有意义了,只能尽量缩短阻塞客户端的时间。
在阻塞客户端时间之前,它是在持久化中消耗的,也就是将内存复制到硬盘的过程。
优化,先从内存拷贝一份到另一个内存空间,然后持久化这个新的内存空间。
这样,持久化过程不会延迟客户端命令,也不会受到客户端命令的影响,从而保证了及时性。
而阻塞客户端的时间只是从内存复制一份数据到内存的时间,相对于整个持久化过程可以忽略不计。
完美!
有了这个完美的计划,我去找我师父邀功。
写副本
我:师傅,我做了一个持久的计划!
主持人:嗯,让我想想...啧啧,复制内存是个好主意,不过有点生疏了。你对操作系统了解不够。
我:啊,为什么?
主持人:想想吧。您现在的目的是隔离持久化和处理客户端命令这两个过程中使用的内存空间,对吗?
我:嗯嗯,是的。
主持人:是的,其实你只需要为持久化创建一个新的流程。不同进程之间的内存是隔离的,即创建一个新进程会将原进程的内存空间完全复制到一个新进程中。
我:啊,和自己复制一段记忆不一样吗?花的时间差不多?
业主:我的图只是给用户一个这样的印象。事实上,linux采用了写时复制技术。当fork离开它的子进程时,并不立即复制内存,只是复制一个映射关系,使它们临时指向同一个内存空间。
Master:当父子进程写入这个内存空间时,它实际上会复制内存,而且是基于页的。
我:我明白了,就是说我可以利用操作系统的进程写的时候复制内存的原理,而不是自己复制所有的内存。因为有了持久化的过程,我觉得对内存的写入不会太多,大部分值都是常量,所以提高了效率。
主持人:是的。
我:太好了!
我迅速修改了方案。当它需要持久化时,我将派生一个子流程来实现这一点。操作系统进程内存隔离的特性为我保证了时效性,写时复制原理为我保证了效率,也就是减少了客户端的阻塞时间。伪代码是这样的。
voidrdbSaveBackground///子进程句柄(使用操作系统的写时复制技术)if((childpid=fork)0)//主方法rdbSave
完美!
我们还没有决定结构。
刚刚访问了持久化的进程,还没有设置写入磁盘的数据格式。
那就订一个。
如果我的Redis内存中只有一条数据,则通过以下命令写入:
设置地兵发牛逼
落在磁盘上的持久性rdb文件将如下所示。
好了,你完成了。我再也不用担心挂电话了。有人会帮我从持久文件中恢复我的内存数据。
但我不在乎持续时间长不长。
什么时候做持久化,我给主人留了个配置。
保存m n
意味着当m秒内对数据集有n次修改时,会自动触发一次持久化。
所有者也可以配置多个这样的配置项目。
我好心地给主人分配了一个默认配置项,并写了一条评论。
#保存债券:
# Intheexamplebelowthebehaviourwillbetosave:
# after 900 secifatleast 1 key changed
# after 300 secifatleast 10keyschanged
# 60秒后指定至少10000个密钥已更改
保存9001
保存30010
储蓄6010000
我觉得以我硕士的英语水平是看得懂的。
好了,这次真的完成了!
这个愚蠢的东西,我会给它一个名字,它叫做RDB。
没什么特别的,其实就是以我的名字开头,Redis DB。
附言
rdb持久化过程也可以手动触发,即直接输入bgsave,与自动触发完全一样。
在redis的源代码中,它被称为bgsaveCommand方法。
整个源代码非常简单易读,但是有很多杂念。