从源码看redis rdb

316 阅读1分钟

简介

rdb用于保存redis数据的快照
优点:
1 一般时候对性能影响较小
2 非常适合做冷备
3 数据恢复快
缺点:
1 如果数据量大时候,会导致cpu和内存占用率升高,影响性能(所以实际操作中,如果开启rdb那么物理内存最好是maxmemory的两倍多)
2 可能会丢失较多数据

触发点

1 执行bgsave和save
2 满足配置的rdb策略
对应配置文件中如下:
save 900 1
save 300 10
save 60 10000

执行bgsave(save类似,只是少了fork)

struct redisCommand redisCommandTable[] = {
...
{"bgsave",bgsaveCommand,-1,"as",0,NULL,0,0,0,0,0},
...
}

void bgsaveCommand(client *c) {
    int schedule = 0;

判断是否有rdb在进行中
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
判断是否有aof在进行中
    } else if (server.aof_child_pid != -1) {
        if (schedule) {
            server.rdb_bgsave_scheduled = 1;
            addReplyStatus(c,"Background saving scheduled");
        } else {
            addReplyError(c,
                "An AOF log rewriting in progress: can't BGSAVE right now. "
                "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
                "possible.");
        }
执行rdb
    } else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
        addReplyStatus(c,"Background saving started");
    } else {
        addReply(c,shared.err);
    }
}

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
...
if ((childpid = fork()) == 0) {
        int retval;

        /* Child */
        关闭子进程复制的socket
        closeListeningSockets(0);
        执行保存
        retval = rdbSave(filename,rsi);
}

int rdbSave(char *filename, rdbSaveInfo *rsi) {
    char tmpfile[256];

  建立一个临时文件
    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
    fp = fopen(tmpfile,"w");
...
把文件描述符复制给rdb
rioInitWithFile(&rdb,fp);
...
设置渐进式fsync,防止一致性的大写入
    if (server.rdb_save_incremental_fsync)
        rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
写入
 if (rdbSaveRio(&rdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) {
        errno = error;
        goto werr;
    }
重命名
if (rename(tmpfile,filename) == -1) {
...
}
}

int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
...
 for (j = 0; j < server.dbnum; j++) {
    
        写入选择的db
        if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
        if (rdbSaveLen(rdb,j) == -1) goto werr;

     
        迭代所有键值写入
        while((de = dictNext(di)) != NULL) {
            sds keystr = dictGetKey(de);
            robj key, *o = dictGetVal(de);
            long long expire;

            initStaticStringObject(key,keystr);
            expire = getExpire(db,&key);
            if (rdbSaveKeyValuePair(rdb,&key,o,expire) == -1) goto werr;

      
        }
        dictReleaseIterator(di);
        di = NULL; /* So that we don't release it again on error. */
    }
...
}

rdb策略

 if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        serverPanic("Can't create event loop timers.");
        exit(1);
    }
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...

        for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;

          如何我们达到了给定的变化数量在给定的秒数而且上一次bgsave成功了,或者已经过了CONFIG_BGSAVE_RETRY_DELAY的秒数
 
            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;
            }
        }

...
}