本文出自 Redis的设计与实现 黄建宏 一书.只是对这部分内容进行了一点小整理.
作为内存数据库,在运行时,Redis会将数据存储到内存中.也就是说,进程一旦退出,Redis存储的数据就会消失.为了解决这个问题,Redis提供了RDB,AOF持久化功能.
通俗地来讲.RDB 持久化功能是通过保存数据库 键值对 来记录数据库的状态不同; AOF 持久化是通过保存服务器所 执行的命令 来记录数据库的状态不同.
RDB:
RDB持久化既可以手动执行,也可以根据配置文件的配置执行.
手动执行有两个命令,一个是 SAVE, 另一个是 BGSAVE.
其中 SAVE 命令会阻塞 Redis 服务进程,直到RDB文件创建完为止,在这个阻塞期间,服务不能接收其他任何命令.
而 BGSAVE 命令会派生一个子进程,不会阻塞其他命令的执行,当 BGSAVE 执行期间,客户端再发送 SAVE 或 BGSAVE 会被服务器拒绝执行.因为服务器禁止这两个命令同时执行,以免产生竞争.
创建 RDB 文件的工作实际由 rdb.c 文件下的 rdbSave 函数来执行, SAVE 和 BGSAVE 会以不同的方式调用这个函数.
RDB 文件的载入一般是在 Redis 服务载入时自动执行.一般启动时,只要检测到配置文件夹下存在rdb文件,就会自动执行.一般可以看到 DB loaded from disk: ... 就是执行打印的日志.且无论何时,载入rdb文件期间服务是被阻塞的.
除了手动执行外,简单的方式是通过配置文件自动间隔性执行,只需按需修改 SNAPSHOTTING 下的 save,stop-writes-on-bgsave-error,dbfilename,dir等属性.每个属性具体含义可查看属性上方相应注释.我们以默认配置为例:
save 900 1
save 300 10
save 60 10000
只要满足下面条件任何一个, BGSAVE 命令就会执行:
1:服务器900秒内对数据库进行了至少1次修改
2:服务器300秒内对数据库进行了至少10修改
3:服务器60秒内对数据库进行了至少10000修改
服务器会根据设置的保存条件,将条件设置为服务器状态的 redisServer 结构的 saveparams 属性:
struct redisServer {
***
// 记录保存条件的数组
struct saveparam *saveparams;
***
}
saveparams 是一个数组,数组中保存着 saveparam 结构,每个 saveparam 结构都保存着一个 save 的数据:
struct saveparam {
time_t seconds;
int changes;
}
除此之外,服务器状态 redisServer 还维持着一个 dirty 计数器 和一个 lastsave 属性
dirty 是记录上一次成功执行 SAVE 或者 BGSAVE 之后,服务器对数据进行了多少次修改.
lastsave 是一个 UNIX 时间戳,记录了上一次执行 SAVE 或 BGSAVE 的时间.
要想做到自动间隔性执行 RDB 持久化,还需要借助 Redis 周期性执行的函数 serverCorn,这个函数每隔 100毫秒执行一次,用于对正在进行的服务进行维护,其中就包括遍历并检查 save 保存是否满足来决定是否执行 BGSAVE 操作.
AOF:
相对于 RDB ,通常 AOF 比 RDB 执行频率更高,且开启了 AOF 时,将会优先使用 AOF 进行持久化,而不会使用 RDB.
服务器在启动时,可以载入 AOF 文件中保存的命令来还原服务器关闭之前的数据状态.一般可以看到日志
DB loaded from append only file: ...
可以通过命令 BGREWRITEAOF 手动进行 AOF 的持久化,也可以配置 redis.conf 文件修改属性 appendonly为 yes,可以通过属性 appendfilename 属性来指定文件名称,appendfsync 来指定何时写入和同步AOF 文件,no-appendfsync-on-rewrite来指定 AOF 重写( rewrite )时是否暂缓文件同步.等属性实现 AOF 的自动化.
AOF 功能的实现可以分为 命令追加,文件写入,文件同步三个步骤.当 AOF 开启式,服务器每次执行完毕一个命令后,会以协议的格式将被执行的命令已追加的方式保存在服务器状态的 redisServer 结构 aof_buf 缓冲区末尾.
struct redisServer {
***
// AOF 缓冲区
sds aof_buf;
***
}
当定时运行的函数 serverCorn 执行 AOF 的写操作时,如果有一些数据已经被写到 aof_buf 缓冲区,会调用 flushAppendOnlyFile() 函数来判断需不需要将 aof_buf 缓冲区的文件持久化道 AOF 文件之中.flushAppendOnlyFile()的行为有服务器状态的 redisServer 结构配置的 appendfsync 的值来决定,值不同flushAppendOnlyFile()的行为就不同.
其值为 always 时: 将aof_buf 缓冲区的所有内容写入并同步到 AOF 文件中.效率最慢,但是最安全.
everysec:将aof_buf 缓冲区的所有内容写入到 AOF 文件中,如果上次同步 AOF 文件时间距离现在间隔1s,则开一个线程进行同步.效率相对较快,但可能会丢失1s的数据
no: 将aof_buf 缓冲区的所有内容写入到 AOF 文件中,不执行同步,合适同步由操作系统来决定.效率最快,但同步时间一般最长,出现故障停机时,会丢失上次同步 AOF 到停机时的所有数据.
appendfsync 默认值为 everysec.
AOF 持久化保存被执行的命令来记录数据库的状态的,随着时间的流逝, AOF 可能会过于臃肿,同步时间就会变得很长,会对 Redis 性能造成影响,为了解决这个问题,Redis 提供了文件重写( rewrite ) 功能.Redis 服务会创建一个新的 AOF 文件代替旧的 AOF 文件,新的文件不会含有任何浪费空间的冗余命令.比如:
rpush list 'a','b'
rpush list 'c','d','e'
lpop list
lpop list
此时数据库的数据应该是 'c','d','e',也就是说,重写过后的 AOF 文件只包含 rpush list 'c','d','e' 命令,这样就能有效的缩小 AOF 文件的大小.在进行 AOF 重写时,不需要对现有的 AOF 文件进行任何的操作,包括读取.而是直接读取数据库的状态进行重写操作.可以通过修改配置文件来定制化重写.比如
auto-aof-rewrite-percentage 100 AOF 文件增长了原大小的 100%时,即由50mb变为100mb就进行重写
auto-aof-rewrite-min-size 64mb AOF文件64mb时即重写,可以适当增加
为防止阻塞, AOF 重写是 Redis 服务开启了一个子进程来进行的,如果在重写的过程中,有新的命令对数据库进行了修改,就可能造成数据不一致的情况.Redis设置了一个 AOF重写缓冲区 来解决这个问题.当Redis服务进行重写时,如果出现新的命令,就会把这个命令加入到 AOF 缓冲区 和 AOF 重写缓冲区,当重写缓冲区命令全部重写后, AOF 重写操作才可以完成.