Redis RDB持久化

來源:互聯網
上載者:User

在啟動並執行情況下,redis將資料以資料結構的形式儲存在記憶體中,為了讓資料在redis重啟之後仍然可用,Redis提供了RDB和AOF兩種持久化方式,本篇講解RDB持久化的工作方式

在運行時,redis將記憶體中的資料庫以快照的方式儲存到磁碟中,在重啟後讀取磁碟中RDB檔案來將redis恢複到原來的狀態 RDB的優缺點 優點

·RDB是一個非常緊湊的檔案,它儲存了redis在某個時間點上的資料集,非常適於備份

·RDB適用於災難恢複,因為其只有一個檔案,而且內容緊湊,可以將其傳送到其他的資料中心用於儲存

·RDB可以最大化redis的效能,執行RDB持久化時只需要fork一個子進程,並由子進程進行持久化工作,父進程不需要處理任何磁碟I/O操作

·RDB在恢複大資料集時比AOF要快 缺點

·如果你盡量避免伺服器故障時遺失資料,那麼RDB並不適合。因為RDB持久化要儲存整個資料集的資料,這不是一個輕鬆的操作,所以不適合頻繁操作,一旦伺服器發生故障有可能會丟失幾分鐘的資料

·每次儲存RDB時,父進程都需要fork一個子進程進行持久化的操作,如果資料集非常的龐大,fork所需的時間可能非常長,這樣可能造成暫停處理用戶端的請求

如果資料集非常巨大,並且 CPU 時間非常緊張的話,那麼這種停止時間甚至可能會長達整整一秒。雖然 AOF 重寫也需要進行 fork() ,但無論 AOF 重寫的執行間隔有多長,資料的耐久性都不會有任何損失 RDB快照

在預設情況下,Redis 將資料庫快照集儲存在名字為 dump.rdb 的二進位檔案中。你可以對 Redis 進行設定,讓它在“N 秒內資料集至少有 M 個改動”這一條件被滿足時,自動儲存一次資料集。你也可以通過調用SAVE 或者BGSAVE ,手動讓 Redis 進行資料集儲存操作
比如說,以下設定會讓 Redis 在滿足“60 秒內有至少有 1000 個鍵被改動”這一條件時,自動儲存一次資料集:
save 60 1000
這種持久化方式被稱為快照(snapshot) 格式

一個RDB檔案可以分為下面幾個部分:

REDIS: 檔案開頭儲存著 REDIS 5個字元,標示這一個RDB檔案的開始

RDB-VERSION:一個四位元組長的以字元表示的整數,記錄了該檔案所使用的 RDB 版本號碼

DB-DATA:這一部分會在一個RDB檔案中出現不止一次,這個部分儲存了redis中非空資料庫的所有資料

SELECT-DB:這個欄位儲存著後面索引值對所屬的資料庫序號

KEY-VALUE-PAIRS:因為空白的資料庫不會被儲存到 RDB 檔案,所以這個部分至少會包含一個索引值對的資料。

每個索引值對的資料使用以下結構來儲存:

OPTIONAL-EXPIRE-TIME 域是可選的,如果鍵沒有設定到期時間,那麼這個域就不會出現;反之,如果這個域出現的話,那麼它記錄著鍵的到期時間,在目前的版本的 RDB 中,到期時間是一個以毫秒為單位的 UNIX 時間戳記

KEY 域儲存著鍵,格式和 REDIS_ENCODING_RAW 編碼的字串對象一樣
TYPE-OF-VALUE 域記錄著 VALUE 域的值所使用的編碼,根據這個域的指示,程式會使用不同的方式來儲存和讀取 VALUE 的值

EOF:標示資料庫內容的結尾,EDIS_RDB_OPCODE_EOF

CHECK-SUM:RDB 檔案所有內容的校正和,一個 uint_64t 類型值
REDIS 在寫入 RDB 檔案時將校正和儲存在 RDB 檔案的末尾,當讀取時,根據它的值對內容進行校正 RDB持久化命令 SAVE

當SAVE命令執行時,Redis伺服器是阻塞的,所以當一個SAVE命令執行時,不會處理另外的SAVE、BGSAVE或者BGREWRITEAOF命令

只有在上一個 SAVE 執行完畢、Redis 重新開始接受請求之後,新的 SAVE 、BGSAVE 或BGREWRITEAOF 命令才會被處理
另外,因為 AOF 寫入由後台線程完成,而 BGREWRITEAOF 則由子進程完成,所以在 SAVE執行的過程中,AOF 寫入和 BGREWRITEAOF 可以同時進行 BGSAVE

在執行 SAVE 命令之前,伺服器會檢查 BGSAVE 是否正在執行當中,如果是的話,伺服器就不調用 rdbSave ,而是向用戶端返回一個出錯資訊,告知在 BGSAVE 執行期間,不能執行SAVE
這樣做可以避免 SAVE 和 BGSAVE 調用的兩個 rdbSave 交叉執行,造成競爭條件

另一方面,當 BGSAVE 正在執行時,調用新 BGSAVE 命令的用戶端會收到一個出錯資訊,告
知 BGSAVE 已經在執行當中
BGREWRITEAOF 和 BGSAVE 不能同時執行:
• 如果 BGSAVE 正在執行,那麼 BGREWRITEAOF 的重寫請求會被延遲到 BGSAVE 執行完畢之後進行,執行 BGREWRITEAOF 命令的用戶端會收到請求被延遲的回複。
• 如果 BGREWRITEAOF 正在執行,那麼調用 BGSAVE 的用戶端將收到出錯資訊,表示這兩個命令不能同時執行。
BGREWRITEAOF 和 BGSAVE 兩個命令在操作方面並沒有什麼衝突的地方,不能同時執行它們只是一個效能方面的考慮:並發出兩個子進程,並且兩個子進程都同時進行大量的磁碟寫入操作,這怎麼想都不會是一個好主意 RDB的實現

RDB 功能最核心的是 rdbSave 和 rdbLoad 兩個函數

下面具體分析rdbSave函數的實現

redis 支援兩種方式進行 RDB:

當前進程執行和後台執行(BGSAVE)

RDB BGSAVE 策略是 fork 出一個子進程,把記憶體中的資料集整個 dump 到硬碟上。兩個情境舉例 redis 伺服器初始化過程中,設定了定時事件,每隔一段時間就會觸發持久化操作;進入定時事件處理常式中,就會 fork 產生子進程執行持久化操作 redis 伺服器預設了 save 指令,用戶端可要求伺服器處理序中斷服務,執行持久化操作

rdbSave函數實現如下:

 
  1: /* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
  2: /*
  3:  * 將資料庫儲存到磁碟上。成功返回 REDIS_OK ,失敗返回 REDIS_ERR 。
  4:  */
  5: int rdbSave(char *filename) {
  6:     dictIterator *di = NULL;
  7:     dictEntry *de;
  8:     char tmpfile[256];
  9:     char magic[10];
 10:     int j;
 11:     long long now = mstime();
 12:     FILE *fp;
 13:     rio rdb;
 14:     uint64_t cksum;
 15: 
 16:     // 以 "temp-<pid>.rdb" 格式建立臨時檔案名稱
 17:     snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
 18:     fp = fopen(tmpfile,"w");
 19:     if (!fp) {
 20:         redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
 21:             strerror(errno));
 22:         return REDIS_ERR;
 23:     }
 24: 
 25:     // 初始化 rio 檔案
 26:     rioInitWithFile(&rdb,fp);
 27:     // 如果有需要的話,設定校正和計算函數
 28:     if (server.rdb_checksum)
 29:         rdb.update_cksum = rioGenericUpdateChecksum;
 30:     // 以 "REDIS <VERSION>" 格式寫入檔案頭,以及 RDB 的版本
 31:     snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);
 32:     if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr;
 33: 
 34:     // 遍曆所有資料庫,儲存它們的資料
 35:     for (j = 0; j < server.dbnum; j++) {
 36:         // 指向資料庫
 37:         redisDb *db = server.db+j;
 38:         // 指向資料庫 key space
 39:         dict *d = db->dict;
 40:         // 資料庫為空白, pass ,處理下個資料庫
 41:         if (dictSize(d) == 0) continue;
 42: 
 43:         // 建立迭代器
 44:         di = dictGetSafeIterator(d);
 45:         if (!di) {
 46:             fclose(fp);
 47:             return REDIS_ERR;
 48:         }
 49: 
 50:         /* Write the SELECT DB opcode */
 51:         // 記錄正在使用的資料庫的號碼
 52:         if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;
 53:         if (rdbSaveLen(&rdb,j) == -1) goto werr;
 54: 
 55:         /* Iterate this DB writing every entry */
 56:         // 將資料庫中的所有節點儲存到 RDB 檔案
 57:         while((de = dictNext(di)) != NULL) {
 58:             // 取出鍵
 59:             sds keystr = dictGetKey(de);
 60:             // 取出值
 61:             robj key, 
 62:                  *o = dictGetVal(de);
 63:             long long expire;
 64:             
 65:             initStaticStringObject(key,keystr);
 66:             // 取出到期時間
 67:             expire = getExpire(db,&key);
 68:             if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;
 69:         }
 70:         dictReleaseIterator(di);
 71:     }
 72:     di = NULL; /* So that we don't release it again on error. */
 73: 
 74:     /* EOF opcode */
 75:     if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;
 76: 
 77:     /* CRC64 checksum. It will be zero if checksum computation is disabled, the
 78:      * loading code skips the check in this case. */
 79:     cksum = rdb.cksum;
 80:     memrev64ifbe(&cksum);
 81:     rioWrite(&rdb,&cksum,8);
 82: 
 83:     /* Make sure data will not remain on the OS's output buffers */
 84:     fflush(fp);
 85:     fsync(fileno(fp));
 86:     fclose(fp);
 87: 
 88:     /* Use RENAME to make sure the DB file is changed atomically only
 89:      * if the generate DB file is ok. */
 90:     // 將臨時檔案 tmpfile 改名為 filename 
 91:     if (rename(tmpfile,filename) == -1) {
 92:         redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
 93:         unlink(tmpfile);
 94:         return REDIS_ERR;
 95:     }
 96: 
 97:     redisLog(REDIS_NOTICE,"DB saved on disk");
 98: 
 99:     // 初始化資料庫資料
100:     server.dirty = 0;
101:     server.lastsave = time(NULL);
102:     server.lastbgsave_status = REDIS_OK;
103: 
104:     return REDIS_OK;
105: 
106: werr:
107:     fclose(fp);
108:     unlink(tmpfile);
109:     redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
110:     if (di) dictReleaseIterator(di);
111:     return REDIS_ERR;
112: }

在rdbSave()中,首先建立一個臨時檔案,在成功save後會改名,然後初始化rio(robust IO,是對IO操作的封裝,在Redis中專門用於ddb save),然後在檔案中首先記錄REDIS MAGIC NUMBER。這裡的rdb_checksum是校正和,每當向硬碟寫時,都會更新校正和,用於後面讀取rdb檔案時校正

dictGetSafeIterator()是得到dict資料結構的一個迭代器,用於遍曆Redis Instance中的資料庫,首先寫入REDIS_RDB_OPCODE_SELECTDB首碼和DB編號。這裡的rdbSaveLen()就是實現長度壓縮編碼的函數,用6bits、14bits或者32bits儲存長度。然後遍曆DB,獲得key和value,還有可能具有的expire time。因為在DB層,key是ads結構的字串,需要先封裝為robj對象,再根據String類型的儲存方式儲存。調用rdbSaveKeyValuePair()解析value類型,根據類型儲存格式

最後寫入校正和,同步內容到硬碟,然後重新命名檔案

以下為bgsave,fork產生一個子進程

 
  1: 
  2: /*
  3:  * 使用子進程儲存資料庫資料,不阻塞主進程
  4:  */
  5: int rdbSaveBackground(char *filename) {
  6:     pid_t childpid;
  7:     long long start;
  8: 
  9:     if (server.rdb_child_pid != -1) return REDIS_ERR;
 10:     
 11:     // 修改伺服器狀態
 12:     server.dirty_before_bgsave = server.dirty;
 13: 
 14:     // 開始時間
 15:     start = ustime();
 16:     // 建立子進程
 17:     if ((childpid = fork()) == 0) {
 18:         int retval;
 19: 
 20:         /* Child */
 21:         // 子進程不接收網路資料
 22:         if (server.ipfd > 0) close(server.ipfd);
 23:         if (server.sofd > 0) close(server.sofd);
 24: 
 25:         // 儲存資料
 26:         retval = rdbSave(filename);
 27:         if (retval == REDIS_OK) {
 28:             size_t private_dirty = zmalloc_get_private_dirty();
 29: 
 30:             if (private_dirty) {
 31:                 redisLog(REDIS_NOTICE,
 32:                     "RDB: %lu MB of memory used by copy-on-write",
<

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.