redis 源碼學習(RDB 持久化)

來源:互聯網
上載者:User

標籤:

redis是個記憶體資料庫,所有的操作都是在記憶體中進行,但是記憶體有個特點是,程式出問題或者系統出問題、重啟,關機都會造成記憶體資料丟失。

所以需要把記憶體中的資料dump到硬碟中備份起來。


RDB持久化,是記憶體資料庫dump到硬碟的過程,其中RDB是個檔案格式,待會介紹。

本文從兩個方向剖析,

1)載入dump.rdb檔案到記憶體中。

2)記憶體資料庫dump到硬碟中dump.rdb檔案。


載入dump.rdb檔案到記憶體

main函數入口:

int main(int argc, char **argv) {    //...        // 從 AOF 檔案或者 RDB 檔案中載入資料        loadDataFromDisk();    //...}
loadDataFromDisk函數就是載入硬碟資料到記憶體(AOF或者RDB),具體的實現來看代碼:

/* Function called at startup to load RDB or AOF file in memory. */void loadDataFromDisk(void) {    // 記錄開始時間    long long start = ustime();    // AOF 持久化已開啟?    if (server.aof_state == REDIS_AOF_ON) {        // 嘗試載入 AOF 檔案        if (loadAppendOnlyFile(server.aof_filename) == REDIS_OK)            // 列印載入資訊,並計算載入耗時間長度度            redisLog(REDIS_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);    // AOF 持久化未開啟    } else {        // 嘗試載入 RDB 檔案        if (rdbLoad(server.rdb_filename) == REDIS_OK) {            // 列印載入資訊,並計算載入耗時間長度度            redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds",                (float)(ustime()-start)/1000000);        } else if (errno != ENOENT) {            redisLog(REDIS_WARNING,"Fatal error loading the DB: %s. Exiting.",strerror(errno));            exit(1);        }    }}
AOF下一篇檔案再剖析他的結構,現在就看RDB檔案的載入:

if (rdbLoad(server.rdb_filename) == REDIS_OK) {// RDB檔案的載入,server.rdb_filename預設值位dump.rdb

/* * 將給定 rdb 中儲存的資料載入到資料庫中。 */int rdbLoad(char *filename) {    uint32_t dbid;    int type, rdbver;    redisDb *db = server.db+0;    char buf[1024];    long long expiretime, now = mstime();    FILE *fp;    rio rdb;    // 開啟 rdb 檔案    if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;    // 初始化寫入流    rioInitWithFile(&rdb,fp);    rdb.update_cksum = rdbLoadProgressCallback;    rdb.max_processing_chunk = server.loading_process_events_interval_bytes;    if (rioRead(&rdb,buf,9) == 0) goto eoferr;    buf[9] = '\0';    // 檢查版本號碼    if (memcmp(buf,"REDIS",5) != 0) {        fclose(fp);        redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file");        errno = EINVAL;        return REDIS_ERR;    }    rdbver = atoi(buf+5);    if (rdbver < 1 || rdbver > REDIS_RDB_VERSION) {        fclose(fp);        redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver);        errno = EINVAL;        return REDIS_ERR;    }    // 將伺服器狀態調整到開始載入狀態    startLoading(fp);    while(1) {        robj *key, *val;        expiretime = -1;        /* Read type.          *         * 讀入類型指示,決定該如何讀入之後跟著的資料。         *         * 這個指示可以是 rdb.h 中定義的所有以         * REDIS_RDB_TYPE_* 為首碼的常量的其中一個         * 或者所有以 REDIS_RDB_OPCODE_* 為首碼的常量的其中一個         */        if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;        // 讀入到期時間值        if (type == REDIS_RDB_OPCODE_EXPIRETIME) {            // 以秒計算的到期時間            if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;            /* We read the time so we need to read the object type again.              *             * 在到期時間之後會跟著一個索引值對,我們要讀入這個索引值對的類型             */            if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;            /* the EXPIRETIME opcode specifies time in seconds, so convert             * into milliseconds.              *             * 將格式轉換為毫秒*/            expiretime *= 1000;        } else if (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {            // 以毫秒計算的到期時間            /* Milliseconds precision expire times introduced with RDB             * version 3. */            if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;            /* We read the time so we need to read the object type again.             *             * 在到期時間之後會跟著一個索引值對,我們要讀入這個索引值對的類型             */            if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;        }                    // 讀入資料 EOF (不是 rdb 檔案的 EOF)        if (type == REDIS_RDB_OPCODE_EOF)            break;        /* Handle SELECT DB opcode as a special case          *         * 讀入切換資料庫指示         */        if (type == REDIS_RDB_OPCODE_SELECTDB) {            // 讀入資料庫號碼            if ((dbid = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR)                goto eoferr;            // 檢查資料庫號碼的正確性            if (dbid >= (unsigned)server.dbnum) {                redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server configured to handle more than %d databases. Exiting\n", server.dbnum);                exit(1);            }            // 在程式內容切換資料庫            db = server.db+dbid;            // 跳過            continue;        }        /* Read key          *         * 讀入鍵         */        if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;        /* Read value          *         * 讀入值         */        if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;        /* Check if the key already expired. This function is used when loading         * an RDB file from disk, either at startup, or when an RDB was         * received from the master. In the latter case, the master is         * responsible for key expiry. If we would expire keys here, the         * snapshot taken by the master may not be reflected on the slave.          *         * 如果伺服器為主節點的話,         * 那麼在鍵已經到期的時候,不再將它們關聯到資料庫中去         */        if (server.masterhost == NULL && expiretime != -1 && expiretime < now) {            decrRefCount(key);            decrRefCount(val);            // 跳過            continue;        }        /* Add the new object in the hash table          *         * 將索引值對關聯到資料庫中         */        dbAdd(db,key,val);        /* Set the expire time if needed          *         * 設定到期時間         */        if (expiretime != -1) setExpire(db,key,expiretime);        decrRefCount(key);    }    /* Verify the checksum if RDB version is >= 5      *     * 如果 RDB 版本 >= 5 ,那麼比對校正和     */    if (rdbver >= 5 && server.rdb_checksum) {        uint64_t cksum, expected = rdb.cksum;        // 讀入檔案的校正和        if (rioRead(&rdb,&cksum,8) == 0) goto eoferr;        memrev64ifbe(&cksum);        // 比對校正和        if (cksum == 0) {            redisLog(REDIS_WARNING,"RDB file was saved with checksum disabled: no check performed.");        } else if (cksum != expected) {            redisLog(REDIS_WARNING,"Wrong RDB checksum. Aborting now.");            exit(1);        }    }    // 關閉 RDB     fclose(fp);    // 伺服器從載入狀態中退出    stopLoading();    return REDIS_OK;eoferr: /* unexpected end of file is handled here with a fatal exit */    redisLog(REDIS_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");    exit(1);    return REDIS_ERR; /* Just to avoid warning */}

函數int rdbLoad(char *filename) 中,很明顯看出rdb的檔案結構,如下:


記憶體資料庫dump到硬碟中dump.rdb檔案

使用函數rdbSave:

/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success  * * 將資料庫儲存到磁碟上。 * * 儲存成功返回 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;        }        /* Write the SELECT DB opcode          *         * 寫入 DB 選取器         */        if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;        if (rdbSaveLen(&rdb,j) == -1) goto werr;        /* Iterate this DB writing every entry          *         * 遍曆資料庫,並寫入每個索引值對的資料         */        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 opcode      *     * 寫入 EOF 代碼     */    if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;    /* CRC64 checksum. It will be zero if checksum computation is disabled, the     * loading code skips the check in this case.      *     * CRC64 校正和。     *     * 如果校正和功能已關閉,那麼 rdb.cksum 將為 0 ,     * 在這種情況下, RDB 載入時會跳過校正和檢查。     */    cksum = rdb.cksum;    memrev64ifbe(&cksum);    rioWrite(&rdb,&cksum,8);    /* Make sure data will not remain on the OS's output buffers */    // 沖洗緩衝,確保資料已寫入磁碟    if (fflush(fp) == EOF) goto werr;    if (fsync(fileno(fp)) == -1) goto werr;    if (fclose(fp) == EOF) goto werr;    /* Use RENAME to make sure the DB file is changed atomically only     * if the generate DB file is ok.      *     * 使用 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;}

以上是大致rdb持最佳化的過程,細節還得繼續扣代碼。


redis 源碼學習(RDB 持久化)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.