redis 备份过程代码分析

在redis中, RDB主要用来进行数据库的全量备份, AOF主要用来进行增量备份, 他们有不同的使用场景, 在真实的线上环境, 比较常见的是结合全量备份和增量备份来实现按时间点的回复。

RDB备份

RDB是对备份时间点的数据库现有数据的一个snapshot, 它的基本原理是遍历数据库内指定的或者全部库, 将数据按照比较紧凑的方式保存在一份数据里面。这里, 比较紧凑的意思是将数据以特定的格式保存, 并且还可以以LZF压缩的方式存储(需要配置)。
RDB 的备份分为2种方式:自动备份和手动备份。
自动备份是在serverCron里面实现, 每当配置文件中指定的自动备份条件满足的时候, 系统自动进行备份。比如, 在配置里面指定如下的备份条件:

save 900 1
save 300 10
save 60 10000

相关的处理代码如下:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    ...
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
        ldbPendingChildren())
    {

        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
            // RDB 或者AOF 子进程返回值的处理
    } else {
        // 当如下的条件之一满足后, 调用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 &&
                (server.unixtime-server.lastbgsave_try >
                 CONFIG_BGSAVE_RETRY_DELAY ||
                 server.lastbgsave_status == C_OK))
            {
                serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes, (int)sp->seconds);
                rdbSaveInfo rsi, *rsiptr;
                rsiptr = rdbPopulateSaveInfo(&rsi);
                rdbSaveBackground(server.rdb_filename,rsiptr);
                break;
            }
        }

同时, 也看到自动备份的入口为rdbSaveBackground。
手工备份, 其入口函数是:

struct redisCommand redisCommandTable[] = {
    {"save",saveCommand,1,
     "admin no-script",
     0,NULL,0,0,0,0,0,0},

    {"bgsave",bgsaveCommand,-1,
     "admin no-script",
     0,NULL,0,0,0,0,0,0},
}

手工备份,有前台备份和后台备份。
前台备份通过rdbSave来实现, 它是由主进程操作实现, 也就是说,在主进程实现RDB前台备份的过程中, 是无法同时接受客户端的相应的, 显然这种方式的缺点很明显:无法应对高并发的场景, 优点就是处理简单;
后台备份是通过rdbSaveBackground来实现, 通过主进程启动一个新的进程, 该子进程调用rdbSave产生备份文件,主子进程之间通过pipe的方式启动一个无名管道,进行信息的传递, 比如,子进程COW数据的大小。 这种备份的好处是不会影响对外提供服务, 因为整个备份过程是异步操作, 缺点就是处理起来相对复杂一些。

AOF备份

AOF备份数据

AOF是将影响到redis数据库状态的所有操作记录下来并写入到一个指定的AOF文件里面, 当需要的时候, 通过回放AOF文件内的一条条redis命令, 来重新构建内存状态, 达到回复数据库的目的。它非常类似于数据库的操作日志的同步与回放。
redis里面的命令非常多, 他们使用一样的接口来处理, 那些命令需要记录在AOF里, 哪些不需要,是通过在调用call()之前是否是定PROPAGATE_AOF flag来表明该操作是否需要记录到AOF, master-slave之间是否需要同步命令, 使用一样的操作方式。

void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
               int flags)
{
    if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF)
        feedAppendOnlyFile(cmd,dbid,argv,argc);
    if (flags & PROPAGATE_REPL)
        replicationFeedSlaves(server.slaves,dbid,argv,argc);
}

feedAppendOnlyFile里面记录了需要记录在AOF中各种命令的存储格式, 也就是AOF协议格式,以便读取的时候能够得到正确的回放。
每一条命令是如果都要写入AOF文件然后返回客户端, 就会在成太频繁的IO操作, 影响redis的系统性能; 不加以控制由系统决定又无法保证数据的落盘, 因此, redis通过如下的配置项来指定:

appendfsync always/everysec/ no

通常, 我们需要续订everysec, 兼顾性能和数据安全性。

AOF随着时间的推移会越变越大, 回放的时间也就会越来越长, 为了提升回放的效率, redis会定期重写AOF。

AOF重写

AOF重写操作的触发有2中方式:用户触发与后台周期检测触发。
用户触发是由命令bgrewriteaof进行, 对应的入口函数为

bgrewriteaofCommand:
struct redisCommand redisCommandTable[] = {
    {"bgrewriteaof",bgrewriteaofCommand,1,
     "admin no-script",
     0,NULL,0,0,0,0,0,0},
}

后台周期性的检测触发, 它的触发点也在serverCron里面, 当没有RDB或者AOF 重写操作的时候, 系统会自动检查AOF的大小以及相对于上次重写时的AOF大小, 决定是否进行重写操作。如下2个配置项可以指定触发的条件:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

代码的实现如下:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    ...
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
        ldbPendingChildren())
    {
        int statloc;
        pid_t pid;

        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
            // RDB 或者AOF 子进程返回值的处理
    } else {
        /* If there is not a background saving/rewrite in progress check if
         * we have to save/rewrite now. */

        /* Trigger an AOF rewrite if needed. */
        if (server.aof_state == AOF_ON &&
            server.rdb_child_pid == -1 &&
            server.aof_child_pid == -1 &&
            server.aof_rewrite_perc &&
            server.aof_current_size > server.aof_rewrite_min_size)
        {
            long long base = server.aof_rewrite_base_size ?
                server.aof_rewrite_base_size : 1;
            long long growth = (server.aof_current_size*100/base) - 100;
            if (growth >= server.aof_rewrite_perc) {
                serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
                rewriteAppendOnlyFileBackground();
            }
        }
    }
    ...
}

这里的操作跟background RDB备份非常相似, 由主进程启动一个子进程, 由子进程完成相关的重写操作rewriteAppendOnlyFile; 在重写操作进行过程中,主进程新的修改数据库的操作被记录在server.aof_buf里面, 当该buffer满之后, 写入到aof_rewrite_buf_blocks 列表内,同时会产生一个写入操作信号, 通知重写子进程重写期间产生的所有操作; 子进程与父进程的通信也是通过pipe的方式传递增量信息与ack信息:
server.aof_pipe_write_data_to_child = fds[1];
server.aof_pipe_read_data_from_parent = fds[0];
server.aof_pipe_write_ack_to_parent = fds[3];
server.aof_pipe_read_ack_from_child = fds[2];
server.aof_pipe_write_ack_to_child = fds[5];
server.aof_pipe_read_ack_from_parent = fds[4];

void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
   listNode *ln = listLast(server.aof_rewrite_buf_blocks);
   aofrwblock *block = ln ? ln->value : NULL;

   while(len) {
       /* If we already got at least an allocated block, try appending
        * at least some piece into it. */
       if (block) {
           unsigned long thislen = (block->free < len) ? block->free : len;
           if (thislen) {  /* The current block is not already full. */
               memcpy(block->buf+block->used, s, thislen);
               block->used += thislen;
               block->free -= thislen;
               s += thislen;
               len -= thislen;
           }
       }

       if (len) { /* First block to allocate, or need another block. */
           int numblocks;

           block = zmalloc(sizeof(*block));
           block->free = AOF_RW_BUF_BLOCK_SIZE;
           block->used = 0;
           listAddNodeTail(server.aof_rewrite_buf_blocks,block);

           /* Log every time we cross more 10 or 100 blocks, respectively
            * as a notice or warning. */
           numblocks = listLength(server.aof_rewrite_buf_blocks);
           if (((numblocks+1) % 10) == 0) {
               int level = ((numblocks+1) % 100) == 0 ? LL_WARNING :
                                                        LL_NOTICE;
               serverLog(level,"Background AOF buffer size: %lu MB",
                   aofRewriteBufferSize()/(1024*1024));
           }
       }
   }

   /* Install a file event to send data to the rewrite child if there is
    * not one already. */
   if (aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0) {
       aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,
           AE_WRITABLE, aofChildWriteDiffData, NULL);
   }
}

备份数据的加载

在main函数内loadDataFromDisk来将备份数据加载到内存中, 如果AOF存在通过AOF文件加载, 否则通过RDB加载。

void loadDataFromDisk(void) {
    long long start = ustime();
    if (server.aof_state == AOF_ON) {
        if (loadAppendOnlyFile(server.aof_filename) == C_OK)
            serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
    } else {
        rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
        if (rdbLoad(server.rdb_filename,&rsi) == C_OK) {
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章