Redis中的持久化策略——RDB與AOF

之前敘述了Redis中的數據庫鍵值對存儲方式以及鍵過期策略。本篇文章將着重介紹一下Redis中的數據持久化策略,這也是Redis中的重中之重。衆所周知,Redis是一種基於內存KV數據庫,也就是說一般情況下,數據都是存儲在內存中,好處在於存取效率相比於磁盤數據庫要高很多,缺點在於內存的易失性,當斷電或者系統崩潰時內存中的數據是無法被記錄下的,也就是可靠性較差。
目前Redis提供兩種持久化方案,一種是RDB,另一種是AOF,兩種策略都可靠但是在原理以及用法上大相徑庭。RDB持久化是通過記錄數據庫的瞬時狀態來實現,默認情況下使用RDB進行持久化的,RDB文件恢復數據庫狀態比AOF文件要效率高,AOF持久化是通過記錄增量的方式實現,由於AOF持久化方案的更新頻率通常比RDB方案高,更加可靠,所以當在redis.conf中開啓appendonly的時候,系統就會優先使用AOF文件來恢復數據庫狀態。

appendonly yes

RDB持久化

RDB持久化本質上類似snapshot的方法,就是將某個時間節點的數據庫狀態用二進制文件保存到磁盤中,當加載RDB二進制文件後就又可以恢復原先的狀態。RDB持久化有兩種觸發機制,一種是手動用SAVE命令和BGSAVE命令實現,另一種是通過配置文件中save選項自動化實現。接下來對兩種分別進行介紹。

SAVE和BGSAVE

SAVE命令和BGSAVE命令都是手動執行RDB持久化的方法,區別在於SAVE命令是在服務器進程中完成的,所以執行命令的過程中服務器無法響應客戶端的其他命令,只能阻塞,而BGSAVE是在子進程中實現RDB持久化,Redis服務器依舊能繼續處理客戶端的請求。接下來看一下具體實現。

void saveCommand(redisClient *c) {
    // BGSAVE 已經在執行中,不能再執行 SAVE
    // 否則將產生競爭條件
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
        return;
    }
    // 執行 
    if (rdbSave(server.rdb_filename) == REDIS_OK) {
        addReply(c,shared.ok);
    } else {
        addReply(c,shared.err);
    }
}

void bgsaveCommand(redisClient *c) {
    // 不能重複執行 BGSAVE
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
    // 不能在 BGREWRITEAOF 正在運行時執行
    } else if (server.aof_child_pid != -1) {
        addReplyError(c,"Can't BGSAVE while AOF log rewriting is in progress");
    // 執行 BGSAVE
        addReplyStatus(c,"Background saving started");
    } else {
        addReply(c,shared.err);
    }
}

/* 
 * 將數據庫保存到磁盤上。
 * 保存成功返回 REDIS_OK ,出錯/失敗返回 REDIS_ERR 。
 */
int rdbSave(char *filename) {
    dictIterator *di = NULL;
    dictEntry *de;
    char tmpfile[256];
    char magic[10];
    int j;
    long long now = mstime();
    FILE *fp;
    rio rdb;
    uint64_t cksum;

    // 創建臨時文件
    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
    fp = fopen(tmpfile,"w");
    if (!fp) {
        redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
            strerror(errno));
        return REDIS_ERR;
    }
    // 初始化 I/O
    rioInitWithFile(&rdb,fp);
    // 設置校驗和函數
    if (server.rdb_checksum)
        rdb.update_cksum = rioGenericUpdateChecksum;
    // 寫入 RDB 版本號
    snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);
    if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr;
    // 遍歷所有數據庫
    for (j = 0; j < server.dbnum; j++) {
        // 指向數據庫
        redisDb *db = server.db+j;
        // 指向數據庫鍵空間
        dict *d = db->dict;
        // 跳過空數據庫
        if (dictSize(d) == 0) continue;
        // 創建鍵空間迭代器
        di = dictGetSafeIterator(d);
        if (!di) {
            fclose(fp);
            return REDIS_ERR;
        }
        /*
         * 寫入 DB 選擇器
         */
        if (rdbSaveType(&rdb,REDIS_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;
            // 根據 keystr ,在棧中創建一個 key 對象
            initStaticStringObject(key,keystr);
            // 獲取鍵的過期時間
            expire = getExpire(db,&key);
            // 保存鍵值對數據
            if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;
        }
        dictReleaseIterator(di);
    }
    di = NULL; /* So that we don't release it again on error. */
    /* 
     * 寫入 EOF 代碼
     */
    if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;

    /* 
     * CRC64 校驗和。
     * 如果校驗和功能已關閉,那麼 rdb.cksum 將爲 0 ,
     * 在這種情況下, RDB 載入時會跳過校驗和檢查。
     */
    cksum = rdb.cksum;
    memrev64ifbe(&cksum);
    rioWrite(&rdb,&cksum,8);
    // 沖洗緩存,確保數據已寫入磁盤
    if (fflush(fp) == EOF) goto werr;
    if (fsync(fileno(fp)) == -1) goto werr;
    if (fclose(fp) == EOF) goto werr;

    /*
     * 使用 RENAME ,原子性地對臨時文件進行改名,覆蓋原來的 RDB 文件。
     */
    if (rename(tmpfile,filename) == -1) {
        redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
        unlink(tmpfile);
        return REDIS_ERR;
    }
    // 寫入完成,打印日誌
    redisLog(REDIS_NOTICE,"DB saved on disk");
    // 清零數據庫髒狀態
    server.dirty = 0;
    // 記錄最後一次完成 SAVE 的時間
    server.lastsave = time(NULL);
    // 記錄最後一次執行 SAVE 的狀態
    server.lastbgsave_status = REDIS_OK;
    return REDIS_OK;

werr:
    // 關閉文件
    fclose(fp);
    // 刪除文件
    unlink(tmpfile);
    redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
    if (di) dictReleaseIterator(di);
    return REDIS_ERR;
}

int rdbSaveBackground(char *filename) {
    pid_t childpid;
    long long start;
    // 如果 BGSAVE 已經在執行,那麼出錯
    if (server.rdb_child_pid != -1) return REDIS_ERR;
    // 記錄 BGSAVE 執行前的數據庫被修改次數
    server.dirty_before_bgsave = server.dirty;
    // 最近一次嘗試執行 BGSAVE 的時間
    server.lastbgsave_try = time(NULL);
    // fork() 開始前的時間,記錄 fork() 返回耗時用
    start = ustime();
    if ((childpid = fork()) == 0) {
        int retval;
        /* Child */
        // 關閉網絡連接 fd
        closeListeningSockets(0);
        // 設置進程的標題,方便識別
        redisSetProcTitle("redis-rdb-bgsave");
        // 執行保存操作
        retval = rdbSave(filename);
        // 打印 copy-on-write 時使用的內存數
        if (retval == REDIS_OK) {
            size_t private_dirty = zmalloc_get_private_dirty();
            if (private_dirty) {
                redisLog(REDIS_NOTICE,
                    "RDB: %zu 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;
        // 如果 fork() 出錯,那麼報告錯誤
        if (childpid == -1) {
            server.lastbgsave_status = REDIS_ERR;
            redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
                strerror(errno));
            return REDIS_ERR;
        }
        // 打印 BGSAVE 開始的日誌
        redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);
        // 記錄數據庫開始 BGSAVE 的時間
        server.rdb_save_time_start = time(NULL);
        // 記錄負責執行 BGSAVE 的子進程 ID
        server.rdb_child_pid = childpid;
        // 關閉自動 rehash
        updateDictResizePolicy();
        return REDIS_OK;
    }
    return REDIS_OK; /* unreached */
}

由上面的實現過程可以看到,在redisServer結構體中維持了dirty變量和lastsave變量,dirty變量表示距離上次執行SAVE命令或BGSAVE命令後數據庫修改的次數,lastsave是上次執行SAVE命令或BGSAVE命令的時間戳。這兩個變量的功能是實現下面的自動RDB持久化。

自動RDB持久化

如果在redis.conf配置文件中出現如下的save配置項,就是會默認在滿足下面三個條件之一的時候執行BGSAVE命令:

save 900 1
save 300 10
save 60 10000
//每個條件用saveparam結構體表示
struct saveparam{
	time_t seconds;	//秒數
	int changes;	//修改數
};
  1. 服務器在900秒之內對數據庫進行至少1次修改
  2. 服務器在300秒之內對數據庫進行至少10次修改
  3. 服務器在60秒之內對數據庫進行至少10000次修改

serverCron函數會默認每隔100ms循環檢查以上三個條件是否滿足,如果nowtime - lastsave >= save.time && save.changes >= dirty 則執行BGSAVE命令。

RDB文件

從上面的rdbSave函數中可以看到寫入RDB文件的順序。具體RDB文件格式如下圖所示。如果鍵值對有expiredtime(並且沒有過期)則將過期時間也放入RDB文件。
RDB文件格式

/* 
 * 將鍵值對的鍵、值、過期時間和類型寫入到 RDB 中。
 * 出錯返回 -1 。
 * 成功保存返回 1 ,當鍵已經過期時,返回 0 。
 */
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
                        long long expiretime, long long now)
{
    /* 
     * 保存鍵的過期時間
     */
    if (expiretime != -1) {
        /*
         * 不寫入已經過期的鍵
         */
        if (expiretime < now) return 0;	//已經過期的鍵值對就不會放到RDB文件中
        if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
        if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
    }
    /* 
     * 保存類型,鍵,值
     */
    if (rdbSaveObjectType(rdb,val) == -1) return -1;
    if (rdbSaveStringObject(rdb,key) == -1) return -1;
    if (rdbSaveObject(rdb,val) == -1) return -1;
    return 1;
}
/* 
 * 將對象的不同類型以及編碼方式寫入RDB文件中
 */
int rdbSaveObjectType(rio *rdb, robj *o) {
    switch (o->type) {
    case REDIS_STRING:
        return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);
    case REDIS_LIST:
        if (o->encoding == REDIS_ENCODING_ZIPLIST)
            return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST);
        else if (o->encoding == REDIS_ENCODING_LINKEDLIST)
            return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST);
        else
            redisPanic("Unknown list encoding");
    case REDIS_SET:
        if (o->encoding == REDIS_ENCODING_INTSET)
            return rdbSaveType(rdb,REDIS_RDB_TYPE_SET_INTSET);
        else if (o->encoding == REDIS_ENCODING_HT)
            return rdbSaveType(rdb,REDIS_RDB_TYPE_SET);
        else
            redisPanic("Unknown set encoding");
    case REDIS_ZSET:
        if (o->encoding == REDIS_ENCODING_ZIPLIST)
            return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET_ZIPLIST);
        else if (o->encoding == REDIS_ENCODING_SKIPLIST)
            return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET);
        else
            redisPanic("Unknown sorted set encoding");
    case REDIS_HASH:
        if (o->encoding == REDIS_ENCODING_ZIPLIST)
            return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH_ZIPLIST);
        else if (o->encoding == REDIS_ENCODING_HT)
            return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH);
        else
            redisPanic("Unknown hash encoding");
    default:
        redisPanic("Unknown object type");
    }
    return -1;
}
/*
 * 將給定對象 o 保存到 rdb 中。
 * 保存成功返回 rdb 保存該對象所需的字節數 ,失敗返回 0 。
 */
int rdbSaveObject(rio *rdb, robj *o) {
    int n, nwritten = 0;
    // 保存字符串對象
    if (o->type == REDIS_STRING) {
        /* Save a string value */
        if ((n = rdbSaveStringObject(rdb,o)) == -1) return -1;
        nwritten += n;
    // 保存列表對象
    } else if (o->type == REDIS_LIST) {
        /* Save a list value */
        if (o->encoding == REDIS_ENCODING_ZIPLIST) {
            size_t l = ziplistBlobLen((unsigned char*)o->ptr)
            // 以字符串對象的形式保存整個 ZIPLIST 列表
            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;
        } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
            list *list = o->ptr;
            listIter li;
            listNode *ln;
            if ((n = rdbSaveLen(rdb,listLength(list))) == -1) return -1;
            nwritten += n;
            // 遍歷所有列表項
            listRewind(list,&li);
            while((ln = listNext(&li))) {
                robj *eleobj = listNodeValue(ln);
                // 以字符串對象的形式保存列表項
                if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                nwritten += n;
            }
        } else {
            redisPanic("Unknown list encoding");
        }
    // 保存集合對象
    } else if (o->type == REDIS_SET) {
        /* Save a set value */
        if (o->encoding == REDIS_ENCODING_HT) {
            dict *set = o->ptr;
            dictIterator *di = dictGetIterator(set);
            dictEntry *de;
            if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) return -1;
            nwritten += n;
            // 遍歷集合成員
            while((de = dictNext(di)) != NULL) {
                robj *eleobj = dictGetKey(de);
                // 以字符串對象的方式保存成員
                if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                nwritten += n;
            }
            dictReleaseIterator(di);
        } else if (o->encoding == REDIS_ENCODING_INTSET) {
            size_t l = intsetBlobLen((intset*)o->ptr);
            // 以字符串對象的方式保存整個 INTSET 集合
            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;
        } else {
            redisPanic("Unknown set encoding");
        }
    // 保存有序集對象
    } else if (o->type == REDIS_ZSET) {
        if (o->encoding == REDIS_ENCODING_ZIPLIST) {
            size_t l = ziplistBlobLen((unsigned char*)o->ptr);
            // 以字符串對象的形式保存整個 ZIPLIST 有序集
            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;
        } else if (o->encoding == REDIS_ENCODING_SKIPLIST) {
            zset *zs = o->ptr;
            dictIterator *di = dictGetIterator(zs->dict);
            dictEntry *de;
            if ((n = rdbSaveLen(rdb,dictSize(zs->dict))) == -1) return -1;
            nwritten += n;
            // 遍歷有序集
            while((de = dictNext(di)) != NULL) {
                robj *eleobj = dictGetKey(de);
                double *score = dictGetVal(de);
                // 以字符串對象的形式保存集合成員
                if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                nwritten += n;
                // 成員分值(一個雙精度浮點數)會被轉換成字符串
                // 然後保存到 rdb 中
                if ((n = rdbSaveDoubleValue(rdb,*score)) == -1) return -1;
                nwritten += n;
            }
            dictReleaseIterator(di);
        } else {
            redisPanic("Unknown sorted set encoding");
        }
    // 保存哈希表
    } else if (o->type == REDIS_HASH) {
        if (o->encoding == REDIS_ENCODING_ZIPLIST) {
            size_t l = ziplistBlobLen((unsigned char*)o->ptr);
            // 以字符串對象的形式保存整個 ZIPLIST 哈希表
            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;
        } else if (o->encoding == REDIS_ENCODING_HT) {
            dictIterator *di = dictGetIterator(o->ptr);
            dictEntry *de;
            if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) return -1;
            nwritten += n;
            // 迭代字典
            while((de = dictNext(di)) != NULL) {
                robj *key = dictGetKey(de);
                robj *val = dictGetVal(de);
                // 鍵和值都以字符串對象的形式來保存
                if ((n = rdbSaveStringObject(rdb,key)) == -1) return -1;
                nwritten += n;
                if ((n = rdbSaveStringObject(rdb,val)) == -1) return -1;
                nwritten += n;
            }
            dictReleaseIterator(di);
        } else {
            redisPanic("Unknown hash encoding");
        }
    } else {
        redisPanic("Unknown object type");
    }
    return nwritten;
}

AOF持久化

AOF持久化是通過記錄Redis服務器所執行的寫命令來記錄服務器的狀態。除了增量式記錄,AOF持久化與RDB持久化最大區別在於AOF持久化的實時性較好。由上面對RDB持久化方法的描述可知,如果不考慮RDB手動持久化,默認情況下RDB自動持久化的最快時間也要到60秒,而AOF基本上可以做到每秒持久化。

AOF持久化實現

AOF持久化實現可以分成三個步驟:命令追加,文件寫入,文件同步。三者關係如下圖所示。接下來會一一進行詳細介紹。
AOF持久化步驟

  1. 命令追加。每當服務器執行完一個寫命令都會將其按照協議格式追加到AOF緩衝區末尾。
  2. 文件寫入。這裏指的是調用write函數將AOF緩衝區數據寫入內核緩衝區,然後就立刻返回(非阻塞)。
  3. 文件同步。這裏指的是將內核緩衝區的數據調用fsync系統調用寫入系統磁盤。Redis有三種持久化策略:always、everysec、no。always表示將AOF緩衝區的數據寫入並立刻fsync同步到磁盤文件,everysec表示將AOF緩衝區的數據寫入內核緩衝區,但是每隔1s調用fsync系統調用寫入同步,no表示將AOF緩衝區數據寫入內核緩衝區,何時同步由操作系統決定。三種同步方式比較:用fsync週期越頻繁,讀寫效率就越差,但是相應的安全性越高,發生宕機時丟失的數據越少。
    io緩衝示意圖

AOF文件載入並數據還原的過程比較簡單,主要過程就是用僞客戶端從AOF文件中一條一條讀取寫命令,然後將命令傳輸到服務器端,服務器端執行即可。

AOF重寫

當系統運行時間過長就會導致AOF文件過大,有時候會影響系統讀寫AOF文件性能,這時候就需要AOF重寫,簡單來說就是將AOF文件壓縮,把當前數據庫的狀態重寫到AOF文件中並更改名字。
當有大量寫操作的時候就會導致調用AOF重寫的線程被長時間阻塞,無法響應客戶端的命令,因此可以將AOF重寫程序放到子進程中進行(這也是BGREWRITEAOF所做的),同時設置一個AOF重寫緩衝區,作用是:在子進程執行AOF重寫過程中,對於客戶端發來的命令,要同時追加到AOF重寫緩衝區和AOF緩衝區,當子進程完成AOF重寫工作後,會將AOF重寫緩衝區中的所有內容寫到新的AOF文件中,此時新AOF文件的狀態就和數據庫中的狀態一致。大概瞭解重寫過程後看看源碼實現。

/* 
 * 將一集足以還原當前數據集的命令寫入到 filename 指定的文件中。
 * 這個函數被 REWRITEAOF 和 BGREWRITEAOF 兩個命令調用。
 * (REWRITEAOF 似乎已經是一個廢棄的命令)
 * 爲了最小化重建數據集所需執行的命令數量,
 * Redis 會盡可能地使用接受可變參數數量的命令,比如 RPUSH 、SADD 和 ZADD 等。
 * 不過單個命令每次處理的元素數量不能超過 REDIS_AOF_REWRITE_ITEMS_PER_CMD 。
 */
int rewriteAppendOnlyFile(char *filename) {
    dictIterator *di = NULL;
    dictEntry *de;
    rio aof;
    FILE *fp;
    char tmpfile[256];
    int j;
    long long now = mstime();
    /*
     * 創建臨時文件
     * 注意這裏創建的文件名和 rewriteAppendOnlyFileBackground() 創建的文件名稍有不同
     */
    snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
    fp = fopen(tmpfile,"w");
    if (!fp) {
        redisLog(REDIS_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno));
        return REDIS_ERR;
    }
    // 初始化文件 io
    rioInitWithFile(&aof,fp);
    // 設置每寫入 REDIS_AOF_AUTOSYNC_BYTES 字節
    // 就執行一次 FSYNC 
    // 防止緩存中積累太多命令內容,造成 I/O 阻塞時間過長
    if (server.aof_rewrite_incremental_fsync)
        rioSetAutoSync(&aof,REDIS_AOF_AUTOSYNC_BYTES);
    // 遍歷所有數據庫
    for (j = 0; j < server.dbnum; j++) {
        char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
        redisDb *db = server.db+j;
        // 指向鍵空間
        dict *d = db->dict;
        if (dictSize(d) == 0) continue;
        // 創建鍵空間迭代器
        di = dictGetSafeIterator(d);
        if (!di) {
            fclose(fp);
            return REDIS_ERR;
        }
        /* 
         * 首先寫入 SELECT 命令,確保之後的數據會被插入到正確的數據庫上
         */
        if (rioWrite(&aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
        if (rioWriteBulkLongLong(&aof,j) == 0) goto werr;
        /* 
         * 遍歷數據庫所有鍵,並通過命令將它們的當前狀態(值)記錄到新 AOF 文件中
         */
        while((de = dictNext(di)) != NULL) {
            sds keystr;
            robj key, *o;
            long long expiretime;
            // 取出鍵
            keystr = dictGetKey(de);
            // 取出值
            o = dictGetVal(de);
            initStaticStringObject(key,keystr);
            // 取出過期時間
            expiretime = getExpire(db,&key);
            /* 
             * 如果鍵已經過期,那麼跳過它,不保存
             */
            if (expiretime != -1 && expiretime < now) continue;
            /*
             * 根據值的類型,選擇適當的命令來保存值
             */
            if (o->type == REDIS_STRING) {
                char cmd[]="*3\r\n$3\r\nSET\r\n";
                if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
                if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
                if (rioWriteBulkObject(&aof,o) == 0) goto werr;
            } else if (o->type == REDIS_LIST) {
                if (rewriteListObject(&aof,&key,o) == 0) goto werr;
            } else if (o->type == REDIS_SET) {
                if (rewriteSetObject(&aof,&key,o) == 0) goto werr;
            } else if (o->type == REDIS_ZSET) {
                if (rewriteSortedSetObject(&aof,&key,o) == 0) goto werr;
            } else if (o->type == REDIS_HASH) {
                if (rewriteHashObject(&aof,&key,o) == 0) goto werr;
            } else {
                redisPanic("Unknown object type");
            }

            /* 
             * 保存鍵的過期時間
             */
            if (expiretime != -1) {
                char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
                // 寫入 PEXPIREAT expiretime 命令
                if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
                if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
                if (rioWriteBulkLongLong(&aof,expiretime) == 0) goto werr;
            }
        }
        // 釋放迭代器
        dictReleaseIterator(di);
    }
    // 沖洗並關閉新 AOF 文件
    if (fflush(fp) == EOF) goto werr;
    if (aof_fsync(fileno(fp)) == -1) goto werr;
    if (fclose(fp) == EOF) goto werr;
    /* 
     * 原子地改名,用重寫後的新 AOF 文件覆蓋舊 AOF 文件
     */
    if (rename(tmpfile,filename) == -1) {
        redisLog(REDIS_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
        unlink(tmpfile);
        return REDIS_ERR;
    }
    redisLog(REDIS_NOTICE,"SYNC append only file rewrite performed");
    return REDIS_OK;
    
werr:
    fclose(fp);
    unlink(tmpfile);
    redisLog(REDIS_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
    if (di) dictReleaseIterator(di);
    return REDIS_ERR;
}

參考:

  1. Redis設計與實現
  2. RDB持久化博客
  3. AOF持久化詳解
  4. TLPI
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章