redis 是基于内存读写的数据库,所有数据都存储在内存中,所以存储的数据大小受到了限制。但redis依然提供了固化功能,与mysql,leveldb等数据库不同的是,redis的存储功能只是用做备份,恢复的功能,全量数据还是存储在内存中。
redis内部提供了两种固化数据的方式,aof 和rdb。
1.aof
数据写入
aof全称appendOnlyFile,要让redis支持aof方式,需要将配置文件的appendonly配置为"yes",并配置appendfsync 为everysec/always/no。everysec是redis推荐的配置,我们默认以这种方式来具体分析。
每次对数据有修改的操作,均会以append的方式写入server.aof_buf中,每秒钟将在时间事件中检查这个缓冲区是否为空,不空则说明有新的更新,将其写入aof文件中(注意write函数并非实时写入硬盘,可以用flush函数来强制刷新未写入的缓存),并触发刷新任务,通知bio模块创建的线程中执行。aof的文件是相对"白话"的,如图是我本地截取的aof文件:
'*'后跟的是当前的参数个数,'$'后跟的是下一个参数的长度,再后来就是参数了。一个redis中有多个数据库,我们需要确定数据增改到那个数据库了,因此第一句为select db的操作。
除了手动配置的刷新aof规则外,客户端可以手动触发bgrewriteaof来覆盖已有的aof文件(未开启aof配置也是支持这个命令),客户端发起此命令后,redis会fork一个子进程来将内存中的数据保存到文件中。那么问题来了,子进程运行过程中,redis主进程依然在提供服务,如果期间写入新的数据,那么主子进程间的数据将会不同步,如何解决?期间的数据会存储在链表数据块server.aof_rewrite_buf_blocks中,当主进程用wait方法检测到子进程退出以后,会调用backgroundRewriteDoneHandler函数,将缓存的数据附加到新的aof文件中。重写aof可以减小aof文件的大小,因为部分数据失效,或者来回更改,在everysec的配置中均会保存下来,但是重写aof之后,无效数据,或者中间状态的修改均会被忽略。
数据载入
redis刚启动的时候会优先检查是否开启aof配置(否则rdb),如果是则加载aof配置文件,每个参数均以'\n'结尾,因此读取/判断合法十分方便。创建一个fakeClient来模拟客户端将数据插入内存中。这里有一个疑问,数据载入的同时,服务依然处理客户端的io任务,这样不怕aof的旧文件写脏用户新插入的数据么。
2.rdb
数据写入
用户可以配置redis中save的参数,格式为save sec commit 代表每sec秒,如果有commit次修改,则由服务自身执行一次bgsave操作。save配置可以有多个,相互之间是或的关系。server.dirty用来记录数据修改次数,bgsave完成以后,会将其置0。
redis的每次写入都是全量操作,步骤如下
1.创建临时文件,写入redis字符串+版本号。
2.写入select db语句,如果当次db没有数据,跳到下一个,没有更多数据库了,跳到步骤5。
3.创建字典的迭代器,循环字典内的元素。
4.redis都是键值对数据,分别写入超时时间(如果有的话),value的类型,key,value,其中key和value尽可能的压缩编码。如果迭代器到达结尾,跳到步骤2。
5.大于4的版本加入了校验码,校验和使用crc计算,用临时文件覆盖正式rdb文件,流程结束。
数据载入
这个也没有悬念了,怎么写入,就怎么加载;读取到数据之后直接调用dbAdd函数把数据插入到字典server.db[dbnum]->dict中。
对比下aof和rdb。
1.aof文件相对于直接保存了语句和中间过程。所以更易于开发者修改查看数据。追加修改的命令,对io,cpu,内存压力均较小,相对来说更吃存储空间。
2.aof的缺点就是rdb的优点了。特别是如果rdb的save参数保存不当,redis会频繁执行bgsave命令,每次都是全量修改,在数据量大,修改频繁的时候会是个灾难。