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

概述

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 重写的执行间隔有多长,数据的耐久性都不会有任何损失。





版权声明:本文为caishenfans原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。