redis源碼剖析(十二)—— RDB持久化

文章目錄

    • RBD文件載入
    • RDB文件分析
    • 源碼分析
      • 核心代碼
    • rdb文件寫入
    • rdb寫入關鍵函數rdbSaveObjectType
      • rdbSaveStringObjectRaw
      • rdbSaveLongLongAsStringObject

為避免數據丟失。將redis中的數據保存到磁盤中,避免數據意外丟失。

RBD文件載入

在redis啟動時檢測是否有rdb文件,有的話會自動載入。

命令作用
save阻塞服務器進程,知道rbd文件創建完成
bgsavefork子進程,由子進程負責創建RDB文件

RDB文件分析

 [root@python redis-4.0.14]# od -c dump.rdb
0000000   R   E   D   I   S   0   0   0   8 372  \t   r   e   d   i   s
0000020   -   v   e   r 006   4   .   0   .   1   4 372  \n   r   e   d
0000040   i   s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e
0000060 302 212  \b 331   ] 372  \b   u   s   e   d   -   m   e   m 302
0000100 210  \0  \f  \0 372  \f   a   o   f   -   p   r   e   a   m   b
0000120   l   e 300  \0 376  \0 373 001  \0  \0 004   n   a   m   e 300
0000140   { 377   8 033   _ 360   I 223 254 343
0000152
[root@python redis-4.0.14]# redis-cli
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set name xxxx
OK
127.0.0.1:6379> save
OK
127.0.0.1:6379> exit
[root@python redis-4.0.14]# od -c dump.rdb
0000000   R   E   D   I   S   0   0   0   8 372  \t   r   e   d   i   s
0000020   -   v   e   r 006   4   .   0   .   1   4 372  \n   r   e   d
0000040   i   s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e
0000060 302   b   | 333   ] 372  \b   u   s   e   d   -   m   e   m 302
0000100   0 356  \f  \0 372  \f   a   o   f   -   p   r   e   a   m   b
0000120   l   e 300  \0 376  \0 373 001  \0  \0 004   n   a   m   e 004
0000140   x   x   x   x 377 314   " 221 277   [ 223 026   $
0000155
  • 0000000 R E D I S 0 0 0 8 372 \t r e d i s
    1、五個字節的REDIS
    2、四個字節版本號
    3、 一個字節的eof常量
    4、八個字節校驗和

  • 004 n a m e 004 0000140 x x x x 377 314 " 221 277 [ 223 026 $
    004是key的長度
    377 314 " 221 277 [ 223 026 $ 八字節長的校驗和

源碼分析

Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success
將數據庫保存到磁盤上。
保存成功返回 REDIS_OK ,出錯/失敗返回 REDIS_ERR 。

int rdbSave(char *filename) {
    // 創建臨時文件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/OrioInitWithFile(&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;
}

核心代碼

// 遍歷所有數據庫for (j = 0; j < server.dbnum; j++) {// 創建鍵空間迭代器di = dictGetSafeIterator(d);if (!di) {fclose(fp);return REDIS_ERR;}/* Write the SELECT DB opcode** 寫入 DB 選擇器*//*  * 遍歷數據庫,并寫入每個鍵值對的數據*/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);}
  • 獲取鍵值和鍵
237 // 計算給定鍵的哈希值
238 #define dictHashKey(d, key) (d)->type->hashFunction(key)
239 // 返回獲取給定節點的鍵
240 #define dictGetKey(he) ((he)->key)
241 // 返回獲取給定節點的值
242 #define dictGetVal(he) ((he)->v.val)

rdb文件寫入

 891 int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,892                         long long expiretime, long long now)893 {894     /* Save the expire time895      *896      * 保存鍵的過期時間897      */898     if (expiretime != -1) {899         /* If this key is already expired skip it900          *901          * 不寫入已經過期的鍵902          */903         if (expiretime < now) return 0;904905         if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;906         if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;907     }908909     /* Save type, key, value910      *911      * 保存類型,鍵,值912      */913     if (rdbSaveObjectType(rdb,val) == -1) return -1;914     if (rdbSaveStringObject(rdb,key) == -1) return -1;915     if (rdbSaveObject(rdb,val) == -1) return -1;916917     return 1;918 }

rdb寫入關鍵函數rdbSaveObjectType

 655 /* Save the object type of object "o".656  *657  * 將對象 o 的類型寫入到 rdb 中658  */659 int rdbSaveObjectType(rio *rdb, robj *o) {660661     switch (o->type) {662663     case REDIS_STRING:664         return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);665666     case REDIS_LIST:667         if (o->encoding == REDIS_ENCODING_ZIPLIST)668             return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST);669         else if (o->encoding == REDIS_ENCODING_LINKEDLIST)670             return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST);671         else672             redisPanic("Unknown list encoding");673674     case REDIS_SET:675         if (o->encoding == REDIS_ENCODING_INTSET)676             return rdbSaveType(rdb,REDIS_RDB_TYPE_SET_INTSET);677         else if (o->encoding == REDIS_ENCODING_HT)678             return rdbSaveType(rdb,REDIS_RDB_TYPE_SET);679         else680             redisPanic("Unknown set encoding");681682     case REDIS_ZSET:683         if (o->encoding == REDIS_ENCODING_ZIPLIST)684             return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET_ZIPLIST);685         else if (o->encoding == REDIS_ENCODING_SKIPLIST)686             return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET);687         else688             redisPanic("Unknown sorted set encoding");689690     case REDIS_HASH:691         if (o->encoding == REDIS_ENCODING_ZIPLIST)692             return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH_ZIPLIST);693         else if (o->encoding == REDIS_ENCODING_HT)694             return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH);695         else696             redisPanic("Unknown hash encoding");697698     default:699         redisPanic("Unknown object type");700     }701702     return -1; /* avoid warning */703 }

rdbSaveStringObjectRaw

 493 /* Like rdbSaveStringObjectRaw() but handle encoded objects */494 /*495  * 將給定的字符串對象 obj 保存到 rdb 中。496  *497  * 函數返回 rdb 保存字符串對象所需的字節數。498  *499  * p.s. 代碼原本的注釋 rdbSaveStringObjectRaw() 函數已經不存在了。500  */501 int rdbSaveStringObject(rio *rdb, robj *obj) {502503     /* Avoid to decode the object, then encode it again, if the504      * object is already integer encoded. */505     // 嘗試對 INT 編碼的字符串進行特殊編碼506     if (obj->encoding == REDIS_ENCODING_INT) {507         return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);508509     // 保存 STRING 編碼的字符串510     } else {511         redisAssertWithInfo(NULL,obj,sdsEncodedObject(obj));512         return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));513     }514 }515

rdbSaveLongLongAsStringObject

 453 /* Save a long long value as either an encoded string or a string.454  *455  * 將輸入的 long long 類型的 value 轉換成一個特殊編碼的字符串,456  * 或者是一個普通的字符串表示的整數,457  * 然后將它寫入到 rdb 中。458  *459  * 函數返回在 rdb 中保存 value 所需的字節數。460  */461 int rdbSaveLongLongAsStringObject(rio *rdb, long long value) {462     unsigned char buf[32];463     int n, nwritten = 0;464465     // 嘗試以節省空間的方式編碼整數值 value466     int enclen = rdbEncodeInteger(value,buf);467468     // 編碼成功,直接寫入編碼后的緩存469     // 比如,值 1 可以編碼為 11 00 0001470     if (enclen > 0) {471         return rdbWriteRaw(rdb,buf,enclen);472473     // 編碼失敗,將整數值轉換成對應的字符串來保存474     // 比如,值 999999999 要編碼成 "999999999" ,475     // 因為這個值沒辦法用節省空間的方式編碼476     } else {477         /* Encode as string */478         // 轉換成字符串表示479         enclen = ll2string((char*)buf,32,value);480         redisAssert(enclen < 32);481         // 寫入字符串長度482         if ((n = rdbSaveLen(rdb,enclen)) == -1) return -1;483         nwritten += n;484         // 寫入字符串485         if ((n = rdbWriteRaw(rdb,buf,enclen)) == -1) return -1;486         nwritten += n;487     }488489     // 返回長度490     return nwritten;491 }492

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/382343.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/382343.shtml
英文地址,請注明出處:http://en.pswp.cn/news/382343.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux操作系統之exec函數族

當我們想在進程中執行另外一個函數或程序時&#xff0c;可以使用exec函數。進程調用exec函數&#xff0c;則該進程中用戶空間所有代碼和數據會完全被新程序替換&#xff0c;但是不會創建新進程&#xff0c;因此進程id不會發生改變。 函數族的頭文件&#xff1a;unistd.h l:li…

redis源碼剖析(十三)—— dump.rdb文件分析

文章目錄操作方式查看rdb文件參考文檔redis作者解釋rdb和aof的不同redisRDB文件格式Sripathi Krishnamredis各個版本變化操作方式 127.0.0.1:9999> flushall OK 127.0.0.1:9999> set name hodge OK 127.0.0.1:9999> save OK查看rdb文件 [rootpython src]# od -c dum…

linux操作系統之子進程回收函數wait和waitpid函數小結

一個進程在終止時會關閉所有的文件描述符&#xff0c;釋放用戶空間分配的內存&#xff0c;但是它的PCB還保留著&#xff0c;內核在其中還保留著進程的一些信息&#xff1a;如果正常終止&#xff0c;則保留著退出狀態&#xff1b;如果異常終止則保存著導致進程種植的信號。 在父…

一鍵登錄云阿里云

免密登錄堡壘機 安裝oathtool和sshpass 這兩個文件安裝比較耗費時間&#xff01; brew install oath-toolkit brew install https://raw.githubusercontent.com/kadwanev/bigboybrew/master/Library/Formula/sshpass.rb阿里云item2一鍵連接 1 #!/bin/bash23 sshpass -p 密碼…

linux操作系統進程間通信IPC之管道pipe及FIFO

linux環境下,各進程相互獨立&#xff0c;如果想要交換兩個進程之間的數據&#xff0c;需要通過內核&#xff0c;在內存中提供一個緩存區&#xff0c;一個進程往緩存區中寫數據&#xff0c;一個往緩存區讀數據&#xff0c;內核提供的這種機制稱為進程間通信&#xff08;IPC&…

MySQL為什么要用數字做自增主鍵?

1.MySQL為什么要用數字做自增主鍵&#xff1f; 首先為什么我們使用的是int類型&#xff0c;而不是varchar類型 int永遠是固定的4個字節&#xff0c;而char類型是1~255字節之間 優點 占用空間小&#xff0c;節省CPU開銷在使用中&#xff0c;通常會在主鍵上建立索引&#xff…

linux操作系統進程間通信IPC之共享存儲映射

&#xff08;1&#xff09;文件存儲映射I/O&#xff08;Memory-mapped I/O&#xff09; 一個磁盤文件與存儲空間中的一個緩存區相對應&#xff0c;這樣可以在不適合read/write函數的情況下&#xff0c;使用地址&#xff08;指針&#xff09;完成I/O操作。具體實現通過內核指定一…

redis源碼剖析(十四)—— dump.rdb文件分析工具

分析rdb文件的工具 安裝 git clone https://github.com/sripathikrishnan/redis-rdb-tools.git sudo pip install --upgrade pip sudo pip install python-lzf分析以n開頭的key rdb --command justkeyvals --key "n*" /home/kou/redis_tar/redis-3.0-annotated/s…

linux操作系統之信號

&#xff08;1&#xff09;信號的概念 信號的特點&#xff1a;簡單&#xff0c;不能攜帶大量信息&#xff0c;滿足某種特定條件才觸發。 信號的機制&#xff1b;“軟中斷”&#xff0c;通過軟件方式實現&#xff0c;具有很強的延時性。每個進程收到的信號&#xff0c;都由內核負…

redis源碼學習筆記目錄

Redis源碼分析&#xff08;零&#xff09;學習路徑筆記 Redis源碼分析&#xff08;一&#xff09;redis.c //redis-server.c Redis源碼分析&#xff08;二&#xff09;redis-cli.c Redis源碼剖析&#xff08;三&#xff09;——基礎數據結構 Redis源碼剖析&#xff08;四&…

linux操作系統信號捕捉函數之sigaction用法小結

&#xff08;1&#xff09;sigaction函數&#xff1a;注冊一個信號捕捉函數&#xff08;不參與捕捉信號&#xff0c;信號由內核捕捉&#xff09;&#xff0c;并修改原來的信號處理動作 &#xff08;2&#xff09;函數原型及頭文件 頭文件&#xff1a;#include<signal.h>…

redis源碼剖析(十五)——客戶端思維導圖整理

redis源碼剖析&#xff08;十五&#xff09;——客戶端執行邏輯結構整理 加載略慢

linux操作系統信號捕捉函數之回調函數小結

&#xff08;1&#xff09;signal 信號捕捉函數&#xff1a;注冊一個信號捕捉函數&#xff08;不參與捕捉&#xff0c;那是內核的事情&#xff09; 函數實現&#xff1a; typedef void(*sighandler_t)(int); //聲明了一個函數指針&#xff08;代表著一類函數&#xff1a;參…

Redis運維和開發學習筆記-全書思維導圖

Redis運維和開發學習筆記-全書思維導圖 圖片過大&#xff0c;無法上傳。 鏈接:https://pan.baidu.com/s/13pnEMBEdLgjZNOOEAuDvEQ 密碼:qhch

linux操作系統之競態條件(時序競態)

&#xff08;1&#xff09;時序競態&#xff1a;前后兩次運行同一個程序&#xff0c;出現的結果不同。 &#xff08;2&#xff09;pause函數&#xff1a;使用該函數會造成進程主動掛起&#xff0c;并等待信號喚醒&#xff0c;調用該系統調用的進程會處于阻塞狀態&#xff08;主…

linux操作系統之全局異步IO及可重入/不可重入函數

&#xff08;1&#xff09;全局變量異步I/O實現父子進程交替數數 1&#xff09;信號捕捉函數 2&#xff09;main函數實現信號交替 3&#xff09;程序實現 1》創建子進程&#xff0c;父進程等待1s&#xff0c;等待子進程完成捕捉函數注冊&#xff08;捕捉信號SIGUSR1&#xff09…

RDB和AOF速度測試

同一臺機器測試 Redis3.2 Redis5.0.7 Linux python 3.10.0-693.11.1.el7.x86_64 #1 SMP Mon Dec 4 23:52:40 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux rdb測試步驟 1. 修改redis.conf配置文件 開啟rdb(測試aof時&#xff0c;注釋掉rdb&#xff0c;并重啟redis) # save &quo…

LInux操作系統之SIGCHLD信號

&#xff08;1&#xff09;SIGCHLD產生條件 1&#xff09;子進程終止的時候 2&#xff09;子進程接收到SIGSTOP信號停止時 3&#xff09;子進程處于停止狀態&#xff0c;接受到SIGCONT后喚醒 &#xff08;2&#xff09;借助SIGCHLD使用waitpid信號實現父進程對子進程的回收 &a…

rdb和aof到底哪個快

rdb和aof到底哪個快&#xff1f; 大多數情況rdb比aof快&#xff01;取決因素是fsync策略 具體選擇aof還是rdb應根據業務場景選擇。糾結于兩者哪個更快意義不大 測試數據 數據量rdb時間rdb文件大小5000076s1.1M100000197s2.1M150000235s3.1M200000305s4.3M 數據量aof時間ao…