Redis持久化
众所周知,Redis是内存数据库,且使用单个线程来处理命令请求。它将自己的数据库状态(非空数据库以及它们的键值对)存储在内存里面。所以如果没有持久化机制,不把数据保存到硬盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。
为了解决这个问题,redis 提供两种方法进行数据持久化,分别是RDB和AOF。RDB可以将Redis在内存中的数据库状态保存到磁盘里面以实现持久化,AOF通过记录写命令达到持久化效果。两种方法都有各自的优点,需要我们在生产环境中依照实际的业务情况进行裁定。服务器按照下面的流程图选择持久化方式:
RDB 持久化
RDB持久化既可以手动执行,也可以依据配置文件选项定期执行。RDB持久化生成的RDB文件是一个二进制文件,通过此二进制文件能够还原数据库状态。
如图:
RDB提供两种方式生成RDB文件,分别通过执行SAVE和BGSAVE命令生产RDB文件。SAVE由服务器进程直接执行保存,它会阻塞服务器的进程。BGSAVE是服务器子进程执行保存操作,它的运行不会阻塞服务器进程的运行。
SVAE 命令
上面已经说过,SAVE命令执行时会阻塞服务器的运行。因此,在服务器执行SAVE命令时,客户端发送的所有命令请求都会被拒绝。只有服务器执行完SAVE命令,服务器才会重新接收客户端发送的命令请求。
BGSAVE 命令
与SAVE相反,BGSAVE由服务器子进程执行,在执行时依然可以接收客户端发送命令并处理。但是,服务器处理SAVE、BGSAVE、BGREWRITEAOF三种方式和平时有些不一样。
对于SAVE,在执行BGSAVE时,客户端发送的SAVE命令会被服务器拒绝。服务器禁止SAVE和BGSAVE命令同时执行是为了防止父进程(服务器进程)和子进行同时执行产生竞争条件
对于BGSAVE。在执行BGSAVE时,客户端发送的BGSAVE命令时同样也是被拒绝。原因是为了防止两个BGSAVE产生竞争条件。
对于BGREWRITEAOF。在执行BGSAVE时,也是被拒绝的。原因是因为BGSAVE和BGREWRITEAOF不能同时执行。
定期持久化原理
在陈述原理之前,先来看下几个与定期持久化密切相关的属性。
saveparams属性
在服务启动后,服务器会读取save的值赋给saveparams。save 是redis.conf
中的一个配置文件,它在服务器中的默认配置为:
save 900 1 //服务器在900秒之内,对数据库进行了至少1次修改。
save 300 10 //服务器在300秒之内,对数据库进行了至少10次修改。
save 60 10000 //服务器在60秒之内,对数据库进行了至少10000次修改。
服务器状态redisServer中saveparams结构如下:
struct redisServer {
// ...
// 记录了保存条件的数组
struct saveparam *saveparams;
// ...
};
struct saveparam {
// 秒数
time_t seconds;
// 修改数
int changes;
};
如果服务器中SAVE属性为默认配置,那么服务器中的状态将会下面这样的。
dirty计数器和lastsave属性
除了saveparams数组之外,服务器状态还维持着一个dirty计数器,以及一个lastsave属性:
dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)。
lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。
dirty和lastsave在服务器状态中的结构如下:
struct redisServer {
// ...
// 修改计数器
long long dirty;
// 上一次执行保存的时间
time_t lastsave;
// ...
};
上面已经介绍完几个重要的属性了,现在开始切入正题了。
如果未开启AOF功能,那么在Redis启动后,Redis的服务器周期性操作函数serverCron默认每隔100毫秒就会执行一次,它的其中一项工作就是检查save选项所设置的保存条件是否已经满足。如果满足的话,就执行BGSAVE命令。下面是ServerCron函数的逻辑代码
def serverCron():
# …
# 遍历所有保存条件
for saveparam in server.saveparams:
# 计算距离上次执行保存操作有多少秒
save_interval = unixtime_now()-server.lastsave
# 如果数据库状态的修改次数超过条件所设置的次数
# 并且距离上次保存的时间超过条件所设置的时间
# 那么执行保存操作
if server.dirty >= saveparam.changes and \
save_interval > saveparam.seconds:
BGSAVE();
# ...
举个例子,如果Redis服务器的当前状态如下图所示
那么当时间来到1378271101,也即是1378270800的301秒之后,服务器将自动执行一次BGSAVE命令,因为saveparams数组的第二个保存条件——300秒之内有至少10次修改——已经被满足。
假设BGSAVE在执行5秒之后完成,那么上图所示的服务器状态将更新为下图所示的状态,其中dirty计数器已经被重置为0,而lastsave属性也被更新为1378271106。
以上就是Redis服务器根据save选项所设置的保存条件,自动执行BGSAVE命令,进行间隔性数据保存的实现原理。
AOF 持久化
AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。AOF持久化命令可以分为命令追加(append)、文件写入、文件同步三个步骤。开启AOF持久化的配置 appendonly yes
命令追加
resist服务器中的结构是这样的
struct redisServer {
// ...
// AOF缓冲区
sds aof_buf;
// ...
}
服务器在执行一个写命令后,会将命令追加到 aof_buf 缓冲区的末尾。
命令的写入
将命令写入到aof文件中去
将aof文件中的命令同步到磁盘
appendfsync配置说明
appendfsync no (同步操作交给数据库)
当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。-
appendfsync everysec (每隔一秒执行一次同步操作)
当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。但是当这一次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就不管会执行多 长时间都会进行。这时候由于在fsync时文件描述符会被阻塞,所以当前的写操作就会阻塞。结论就是,在绝大多数情况下,Redis会每隔一秒进行一 次fsync。在最坏的情况下,两秒钟会进行一次fsync操作。这一操作在大多数数据库系统中被称为group commit,就是组合多次写操作的数据,一次性将日志写到磁盘。
appendfsync always (每一次写操作都会执行同步操作)
置appendfsync为always时,每一次写操作都会调用一次fsync,这时数据是最安全的,当然,由于每次都会执行fsync,所以其性能也会受到响。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。