redis.conf之save配置項解讀

一見發表於2019-01-07

配置示例:

save 900 1

save 300 10

save 60 3600

 

配置解讀:

1) “save 900 1”表示如果900內至少1個key發生變化(新增、修改和刪除),則重寫rdb檔案;

2) “save 300 10”表示如果每300內至少10個key發生變化(新增、修改和刪除),則重寫rdb檔案;

3) “save 60 3600”表示如果每60內至少10000個key發生變化(新增、修改和刪除),則重寫rdb檔案。

 

作用:

控制什麼時候生成rdb檔案(快照,也可叫Checkpoint,即檢查點)。

 

程式啟動的時候,會將每一行save讀進到型別為struct saveparam的陣列中。這個沒有排序,依在redis.conf中的先後順序。在檢查時,只要滿足就不會再檢查下一條規則。

 

配置策略:

如果同時開啟了aof,則可考慮將save的引數調大一點,以減少寫rdb帶來的壓力。實際上如果開啟了aof,redis在啟動時只會讀取aof檔案,而不會讀取rdb檔案:

// Function called at startup to load RDB or AOF file in memory.

void loadDataFromDisk(void) {    

    if (server.aof_state == AOF_ON) {      

        // 允許空的aof檔案,

        // 如果讀取aof檔案出錯,則呼叫exit(1)直接退出程式

        if (loadAppendOnlyFile(server.aof_filename) == C_OK)

            serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);

    } else {

         if (rdbLoad(server.rdb_filename,&rsi) == C_OK) {

    }

}

 

// #define AOF_OFF 0             /* AOF is off */

// #define AOF_ON 1              /* AOF is on */

void loadServerConfigFromString(char *config) {

    。。。。。。

    } else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {

        int yes;

        if ((yes = yesnotoi(argv[1])) == -1) {

            err = "argument must be 'yes' or 'no'"; goto loaderr;

        }

         server.aof_state = yes ? AOF_ON : AOF_OFF;

    }

    。。。。。。

}

 

int yesnotoi(char *s) {

    if (!strcasecmp(s,"yes")) return 1;

    else if (!strcasecmp(s,"no")) return 0;

    else return -1;

}

 

呼叫順序:

main()/server.c ->

aeMain()/ae.c -> while (!stop) { aeProcessEvents()/ae.c } ->

serverCron()/server.c -> rdbSaveBackground()/server.c

 

注:

aeProcessEvents可看作是個epoll_wait呼叫,在Linux上實際正是epoll_wait呼叫,而在Solaris上則是port_getn呼叫。

 

相關原始碼:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {

    。。。。。。

    // Check if a background saving or AOF rewrite in progress terminated.

    // 如果已有rdb和aof程式,檢查程式是否已退出。

    // 如果已退出,則會善後處理,否則什麼也不做,等待下一次迴圈時再次檢查

    if (server.rdb_child_pid != -1 ||

        server.aof_child_pid != -1 ||

         ldbPendingChildren()) {

        int statloc;

        pid_t pid;

        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {

            int exitcode = WEXITSTATUS(statloc);

            。。。。。。

        }

    } else {

        // If there is not a background saving/rewrite

        // in progress check if we have to save/rewrite now.

        // 按在redis.conf中定義的順序依次遍歷每一行配置項

        // 最終是否進行寫rdb操作(即生成快照檔案),不僅由redis.conf

        // 中的配置項決定,還要看上一次操作的結果和狀態。

        for (j = 0; j < server.saveparamslen; j++) {

            struct saveparam *sp = server.saveparams+j;

            // Save if we reached the given amount of changes,

            // the given amount of seconds, and if the latest bgsave was

            // successful or if, in case of an error, at least

            // CONFIG_BGSAVE_RETRY_DELAY seconds already elapsed.

            // CONFIG_BGSAVE_RETRY_DELAY(5): Wait a few secs before trying again.

            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; // 遇到一條滿足的即結束處理,因為已沒有必要判斷是否滿足下一條配置規則

            }

        }

    }

    。。。。。。     

}

 

// rdb.c

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {

    。。。。。。

    server.lastbgsave_try = time(NULL);

    。。。。。。

    // 建立寫rdb的子程式

    if ((childpid = fork()) == 0) {

        redisSetProcTitle("redis-rdb-bgsave");

        retval = rdbSave(filename,rsi);

    }

    。。。。。。

}

 

/* Save the DB on disk. Return C_ERR on error, C_OK on success. */

// rdb.c

// rdbSave呼叫rdbSaveRio將資料寫入到rdb檔案中

int rdbSave(char *filename, rdbSaveInfo *rsi) {

    。。。。。。

    // 寫rdb檔案

    if (rdbSaveRio(&rdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) {

        errno = error;

        goto werr;

    }

    。。。。。。

    serverLog(LL_NOTICE,"DB saved on disk");

    server.dirty = 0;

    server.lastsave = time(NULL);

    server.lastbgsave_status = C_OK;

    return C_OK;

}

 

/* Produces a dump of the database in RDB format sending it to the specified

 * Redis I/O channel. On success C_OK is returned, otherwise C_ERR

 * is returned and part of the output, or all the output, can be

 * missing because of I/O errors.

 *

 * When the function returns C_ERR and if 'error' is not NULL, the

 * integer pointed by 'error' is set to the value of errno just after the I/O

 * error. */

int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {

    for (j = 0; j < server.dbnum; j++) {

        。。。。。。

        // Iterate this DB writing every entry

        while((de = dictNext(di)) != NULL) {

            。。。。。。

            // 將一對對KV寫入到rdb檔案

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

            。。。。。。

        }

    }

    。。。。。。

werr:

    if (error) *error = errno;

    if (di) dictReleaseIterator(di);

    return C_ERR;

}

 

// 以SADD命令為例,所有寫操作,均會修改dirty 的值

void saddCommand(client *c) {

    。。。。。。

    for (j = 2; j < c->argc; j++) {

        if (setTypeAdd(set,c->argv[j]->ptr)) added++;

    }

    。。。。。。

    server.dirty += added;

    addReplyLongLong(c,added);

}

 

相關文章