您的位置:首页 > 数据库 > Redis

Redis中的RDB持久化和AOF持久化(一)

2015-04-07 19:52 169 查看

概述

Redis是一种内存数据库,运行时数据和状态都保存在内存中,为了避免服务器进程结束而导致的数据丢失,需要将数据保存到磁盘上。Redis提供了两种策略,分别是RDB持久化和AOF持久化。本文先介绍RDB持久化。

RDB持久化

手动创建RDB文件的两个命令是SAVE和BGSAVE,他们的区别是SAVE在主进程中进行文件写入,保存时会阻塞主进程,使其不能执行任何其他操作。BGSAVE是fork一个子进程来保存,具体过程如下:

redis调用fork,现在有了子进程和父进程。

父进程继续处理client请求,子进程负责将内存内容写入到临时文件。由于os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数 据是fork时刻整个数据库的一个快照。

当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。

/*
* 使用子进程保存数据库数据,不阻塞主进程
*/
int rdbSaveBackground(char *filename) {
pid_t childpid;
long long start;

if (server.rdb_child_pid != -1) return REDIS_ERR;

// 修改服务器状态
server.dirty_before_bgsave = server.dirty;

// 开始时间
start = ustime();
// 创建子进程
if ((childpid = fork()) == 0) {
int retval;

/* Child */
// 子进程不接收网络数据
if (server.ipfd > 0) close(server.ipfd);
if (server.sofd > 0) close(server.sofd);

// 保存数据
retval = rdbSave(filename);
if (retval == REDIS_OK) {
size_t private_dirty = zmalloc_get_private_dirty();

if (private_dirty) {
redisLog(REDIS_NOTICE,
"RDB: %lu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
}

// 退出子进程
exitFromChild((retval == REDIS_OK) ? 0 : 1);
} else {
/* Parent */
// 记录最后一次 fork 的时间
server.stat_fork_time = ustime()-start;

// 创建子进程失败时进行错误报告
if (childpid == -1) {
redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
strerror(errno));
return REDIS_ERR;
}

redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);

// 记录保存开始的时间
server.rdb_save_time_start = time(NULL);
// 记录子进程的 id
server.rdb_child_pid = childpid;
// 在执行时关闭对数据库的 rehash
// 避免 copy-on-write
updateDictResizePolicy();

return REDIS_OK;
}

return REDIS_OK; /* unreached */
}


除了手动保存之外,RDB还支持自动间隔保存。如果我们用如下命令设置服务器:

save 300 1
save 800 40


表示只要满足以下两个条件之一,就会执行BGSAVE命令

服务器在300秒之内,对数据库至少进行了1次修改
服务器在800秒之内,对数据库至少进行了40次修改

在服务器的结构体redisServer的saveparams数组中,保存了以上两个执行条件。另外还保存着一个dirty计数器,以及lastsave属性。dirty计数器记录了距离上一次保存之后,服务器对数据库进行了多少次修改。lastsave记录了上一次保存的时间。在周期性调用的函数serverCron中,每次都会检查dirty和lastsave,如果满足了预设的保存条件就调用BGSAVE来保存。

以下是serverCron函数中的一段代码:

/* If there is not a background saving/rewrite in progress check if
* we have to save/rewrite now */
// 如果有需要,开始 RDB 文件的保存
for (j = 0; j < server.saveparamslen; j++) {
struct saveparam *sp = server.saveparams+j;

if (server.dirty >= sp->changes &&
server.unixtime-server.lastsave > sp->seconds) {
redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
sp->changes, sp->seconds);
rdbSaveBackground(server.rdb_filename);//调用BGSAVE函数
break;
}
}


RDB的优势

一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这样非常方便进行备份。比如你可能打算没1天归档一些数据。

方便备份,我们可以很容易的将一个一个RDB文件移动到其他的存储介质上

RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。

RDB的劣势

如果你需要尽量避免在服务器故障时丢失数据,那么 RDB 不适合你。 虽然 Redis 允许你设置不同的保存点(save point)来控制保存 RDB 文件的频率, 但是, 因为RDB 文件需要保存整个数据集的状态, 所以它并不是一个轻松的操作。 因此你可能会至少 5 分钟才保存一次 RDB 文件。 在这种情况下, 一旦发生故障停机, 你就可能会丢失好几分钟的数据。

每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端; 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。 虽然 AOF 重写也需要进行 fork() ,但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: