redisaof持久化

晴天哥發表於2018-06-17

redis aof快取資料結構

 redis用於儲存aof記憶體資料的資料結構是aof_buf資料結構,所有資料先追加到記憶體的aof_buf後,再通過定時任務檢查是否能夠持久化到磁碟檔案當中。

struct redisServer {
    // AOF 緩衝區
    sds aof_buf;      /* AOF buffer, written before entering the event loop */

redis aof記憶體化

 redis aof記憶體化的操作主要有以下三部曲:

  • 執行redis命令後開始儲存資料至記憶體當中的aof_buf當中
  • 將執行的命令解析成redis的命令格式// 例如 $3
    SET
    $3
    KEY
    $5
    VALUE
  • 儲存資料至aof_buf當中
/* 
 * 將指定命令(以及執行該命令的上下文,比如資料庫 id 等資訊)傳播到 AOF 和 slave 。
  * FLAG 可以是以下標識的 xor :
 * + REDIS_PROPAGATE_NONE (no propagation of command at all)
 *   不傳播
 * + REDIS_PROPAGATE_AOF (propagate into the AOF file if is enabled)
 *   傳播到 AOF
 * + REDIS_PROPAGATE_REPL (propagate into the replication link)
 *   傳播到 slave
 */
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
               int flags)
{
    // 傳播到 AOF
    if (server.aof_state != REDIS_AOF_OFF && flags & REDIS_PROPAGATE_AOF)
        feedAppendOnlyFile(cmd,dbid,argv,argc);
}

 執行的操作主要是解析成redis的命令格式並儲存到記憶體的aof_buf當中。

/*
 * 將命令追加到 AOF 檔案中,
 * 如果 AOF 重寫正在進行,那麼也將命令追加到 AOF 重寫快取中。
 */
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
    sds buf = sdsempty();
    robj *tmpargv[3];

    /* 
     * 使用 SELECT 命令,顯式設定資料庫,確保之後的命令被設定到正確的資料庫
     */
    if (dictid != server.aof_selected_db) {
        char seldb[64];

        snprintf(seldb,sizeof(seldb),"%d",dictid);
        buf = sdscatprintf(buf,"*2
$6
SELECT
$%lu
%s
",
            (unsigned long)strlen(seldb),seldb);

        server.aof_selected_db = dictid;
    }

    // EXPIRE 、 PEXPIRE 和 EXPIREAT 命令
    if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
        cmd->proc == expireatCommand) {
        /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT 
         *
         * 將 EXPIRE 、 PEXPIRE 和 EXPIREAT 都翻譯成 PEXPIREAT
         */
        buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);

    // SETEX 和 PSETEX 命令
    } else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
        /* Translate SETEX/PSETEX to SET and PEXPIREAT 
         *
         * 將兩個命令都翻譯成 SET 和 PEXPIREAT
         */

        // SET
        tmpargv[0] = createStringObject("SET",3);
        tmpargv[1] = argv[1];
        tmpargv[2] = argv[3];
        buf = catAppendOnlyGenericCommand(buf,3,tmpargv);

        // PEXPIREAT
        decrRefCount(tmpargv[0]);
        buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);

    // 其他命令
    } else {
        buf = catAppendOnlyGenericCommand(buf,argc,argv);
    }

    /* 
     * 將命令追加到 AOF 快取中,
     * 在重新進入事件迴圈之前,這些命令會被沖洗到磁碟上,
     * 並向客戶端返回一個回覆。
     */
    if (server.aof_state == REDIS_AOF_ON)
        server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));

    /* 
     * 如果 BGREWRITEAOF 正在進行,
     * 那麼我們還需要將命令追加到重寫快取中,
     * 從而記錄當前正在重寫的 AOF 檔案和資料庫當前狀態的差異。
     */
    if (server.aof_child_pid != -1)
        aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));

    // 釋放
    sdsfree(buf);
}

 解析成redis的命令格式:例如 $3
SET
$3
KEY
$5
VALUE

/*
 * 根據傳入的命令和命令引數,將它們還原成協議格式。
 */
sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
    char buf[32];
    int len, j;
    robj *o;

    // 重建命令的個數,格式為 *<count>

    // 例如 *3

    buf[0] = `*`;
    len = 1+ll2string(buf+1,sizeof(buf)-1,argc);
    buf[len++] = `
`;
    buf[len++] = `
`;
    dst = sdscatlen(dst,buf,len);

    // 重建命令和命令引數,格式為 $<length>
<content>

    // 例如 $3
SET
$3
KEY
$5
VALUE

    for (j = 0; j < argc; j++) {
        o = getDecodedObject(argv[j]);

        // 組合 $<length>

        buf[0] = `$`;
        len = 1+ll2string(buf+1,sizeof(buf)-1,sdslen(o->ptr));
        buf[len++] = `
`;
        buf[len++] = `
`;
        dst = sdscatlen(dst,buf,len);

        // 組合 <content>

        dst = sdscatlen(dst,o->ptr,sdslen(o->ptr));
        dst = sdscatlen(dst,"
",2);

        decrRefCount(o);
    }

    // 返回重建後的協議內容
    return dst;
}

redis aof持久化

 serverCron內部定期執行flushAppendOnlyFile,這裡的if判斷是判斷是否延遲執行,暫且忽略這個判斷而認為每次都會進行aof持久化。

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    // 根據 AOF 政策,
    // 考慮是否需要將 AOF 緩衝區中的內容寫入到 AOF 檔案中
    if (server.aof_flush_postponed_start) flushAppendOnlyFile(0);
}

 執行aof記憶體資料持久化的過程,考慮異常情況的情況主要分為以下步驟:

  • 寫入aof_buf資料到aof_fd代表的aof持久化檔案當中
  • 處理aof寫入異常的情況,嘗試修復失敗後會移除失敗的命令
  • 更新aof相關的統計引數
  • 如果aof_buf資料過大那麼就情況aof_buf的內容
void flushAppendOnlyFile(int force) {
    ssize_t nwritten;
    int sync_in_progress = 0;

    // 緩衝區中沒有任何內容,直接返回
    if (sdslen(server.aof_buf) == 0) return;

    // 策略為每秒 FSYNC 
    if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
        // 是否有 SYNC 正在後臺進行?
        sync_in_progress = bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0;

    // 每秒 fsync ,並且強制寫入為假
    if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {

        /* 
         * 當 fsync 策略為每秒鐘一次時, fsync 在後臺執行。
         *
         * 如果後臺仍在執行 FSYNC ,那麼我們可以延遲寫操作一兩秒
         * (如果強制執行 write 的話,伺服器主執行緒將阻塞在 write 上面)
         */
        if (sync_in_progress) {

            // 有 fsync 正在後臺進行 。。。

            if (server.aof_flush_postponed_start == 0) {
                /* 
                 * 前面沒有推遲過 write 操作,這裡將推遲寫操作的時間記錄下來
                 * 然後就返回,不執行 write 或者 fsync
                 */
                server.aof_flush_postponed_start = server.unixtime;
                return;

            } else if (server.unixtime - server.aof_flush_postponed_start < 2) {
                /* 
                 * 如果之前已經因為 fsync 而推遲了 write 操作
                 * 但是推遲的時間不超過 2 秒,那麼直接返回
                 * 不執行 write 或者 fsync
                 */
                return;

            }

            /* 
             * 如果後臺還有 fsync 在執行,並且 write 已經推遲 >= 2 秒
             * 那麼執行寫操作(write 將被阻塞)
             */
            server.aof_delayed_fsync++;
        }
    }

    /* 
     * 執行到這裡,程式會對 AOF 檔案進行寫入。
     *
     * 清零延遲 write 的時間記錄
     */
    server.aof_flush_postponed_start = 0;

    /* 
     * 執行單個 write 操作,如果寫入裝置是物理的話,那麼這個操作應該是原子的
     *
     * 當然,如果出現像電源中斷這樣的不可抗現象,那麼 AOF 檔案也是可能會出現問題的
     * 這時就要用 redis-check-aof 程式來進行修復。
     */
    nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
    if (nwritten != (signed)sdslen(server.aof_buf)) {

        static time_t last_write_error_log = 0;
        int can_log = 0;

        // 將日誌的記錄頻率限制在每行 AOF_WRITE_LOG_ERROR_RATE 秒
        if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
            can_log = 1;
            last_write_error_log = server.unixtime;
        }

        // 如果寫入出錯,那麼嘗試將該情況寫入到日誌裡面
        if (nwritten == -1) {
            if (can_log) {
                redisLog(REDIS_WARNING,"Error writing to the AOF file: %s",
                    strerror(errno));
                server.aof_last_write_errno = errno;
            }
        } else {
            if (can_log) {
                redisLog(REDIS_WARNING,"Short write while writing to "
                                       "the AOF file: (nwritten=%lld, "
                                       "expected=%lld)",
                                       (long long)nwritten,
                                       (long long)sdslen(server.aof_buf));
            }

            // 嘗試移除新追加的不完整內容
            if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {
                if (can_log) {
                    redisLog(REDIS_WARNING, "Could not remove short write "
                             "from the append-only file.  Redis may refuse "
                             "to load the AOF the next time it starts.  "
                             "ftruncate: %s", strerror(errno));
                }
            } else {
                /* If the ftrunacate() succeeded we can set nwritten to
                 * -1 since there is no longer partial data into the AOF. */
                nwritten = -1;
            }
            server.aof_last_write_errno = ENOSPC;
        }

        // 處理寫入 AOF 檔案時出現的錯誤
        if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
            redisLog(REDIS_WARNING,"Can`t recover from AOF write error when the AOF fsync policy is `always`. Exiting...");
            exit(1);
        } else {
            server.aof_last_write_status = REDIS_ERR;

            /* Trim the sds buffer if there was a partial write, and there
             * was no way to undo it with ftruncate(2). */
            if (nwritten > 0) {
                server.aof_current_size += nwritten;
                sdsrange(server.aof_buf,nwritten,-1);
            }
            return; /* We`ll try again on the next call... */
        }
    } else {
        /* Successful write(2). If AOF was in error state, restore the
         * OK state and log the event. */
        // 寫入成功,更新最後寫入狀態
        if (server.aof_last_write_status == REDIS_ERR) {
            redisLog(REDIS_WARNING,
                "AOF write error looks solved, Redis can write again.");
            server.aof_last_write_status = REDIS_OK;
        }
    }

    // 更新寫入後的 AOF 檔案大小
    server.aof_current_size += nwritten;

    /* 
     * 如果 AOF 快取的大小足夠小的話,那麼重用這個快取,
     * 否則的話,釋放 AOF 快取。
     */
    if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
        // 清空快取中的內容,等待重用
        sdsclear(server.aof_buf);
    } else {
        // 釋放快取
        sdsfree(server.aof_buf);
        server.aof_buf = sdsempty();
    }

    /* 
     * 如果 no-appendfsync-on-rewrite 選項為開啟狀態,
     * 並且有 BGSAVE 或者 BGREWRITEAOF 正在進行的話,
     * 那麼不執行 fsync 
     */
    if (server.aof_no_fsync_on_rewrite &&
        (server.aof_child_pid != -1 || server.rdb_child_pid != -1))
            return;

    // 總是執行 fsnyc
    if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
         aof_fsync(server.aof_fd); /* Let`s try to get this data on the disk */

        // 更新最後一次執行 fsnyc 的時間
        server.aof_last_fsync = server.unixtime;

    // 策略為每秒 fsnyc ,並且距離上次 fsync 已經超過 1 秒
    } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
                server.unixtime > server.aof_last_fsync)) {
        // 放到後臺執行
        if (!sync_in_progress) aof_background_fsync(server.aof_fd);
        // 更新最後一次執行 fsync 的時間
        server.aof_last_fsync = server.unixtime;
    }
}


相關文章