逐字節講解 Redis 持久化(RDB 和 AOF)的文件格式

前言

相信各位對 Redis 的這兩種持久化機制都不陌生,簡單來說,RDB 就是對數據的全量備份,AOF 則是增量備份,而從 4.0 版本開始引入了混合方式,以 7.2.3 版本為例,會生成三類文件:RDB、AOF 和記錄 aof 文件的元數據信息文件,如下圖所示,這時的 AOF 可以看作是一種差異備份。

image-20231117142130770

接下來本文將結合具體的備份文件,通過分析其結構,從另一種角度來看兩種持久化方式的差異。

RDB

首先是對 RDB 全量備份文件的解析,想要生成 RDB 文件,有兩種方式,一種是手動方式:使用 save(阻塞)或者 bgsave(非阻塞)命令生成,一種是在配置文件中增加save m n(表示在 m 內,至少出現了 n 次變更就會執行 bgsave 命令)配置來實現。

下面就以一個具體的dump.rdb(在 0 號庫中有一條鍵為 hello,值為 world 的記錄)文件為例來解析其文件格式,由于 RDB 文件是二進制格式,這里使用了一個在線的十六進制編輯器進行查看:

image-20231117151039644

下文均是結合 Redis 7.2.3 版本的源碼的 rdb.c 文件進行解析,對應源碼地址。

0x00 Redis 版本

52 45 44 49 53 30 30 31 31,根據源碼snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);可以看到這里前五位是固定值REDIS,后四位用于標識RDB的版本對應11。

0x01 輔助信息

這部分涉及數據較多,先放出源碼:

if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb,"redis-bits",redis_bits) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb,"ctime",time(NULL)) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb,"used-mem",zmalloc_used_memory()) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb, "aof-base", aof_base) == -1) return -1;

結合編輯器右側的信息,可以發現這部分數據下圖中選中的數據:

在這里插入圖片描述

  1. redis-ver(Redis 版本)

    這部分對應FA 09 72 65 64 69 73 2D 76 65 72 05 37 2E 32 32 2E 33,其中開頭的FA(250)代表這部分數據是 AUX 屬性字段,根據源碼#define RDB_OPCODE_AUX 250可以了解到。然后是09 72 65 64 69 73 2D 76 65 72,09 代表隨后的 9個字節是屬性名,即redis-ver,最后是05 37 2E 32 32 2E 33,其中 05 代表隨后的 5 個字節是屬性名對應的字段值,即 Redis 的版本號7.2.3

  2. redis-bits(位架構)

    這部分對應FA 0A 72 65 64 69 73 2D 62 69 74 73 C0 40。參考 1 可知開始的FA代表AUXOA代表隨后的 10 字節是屬性名,即redis-bits。但是隨后的C0就不再是代表值的長度了,這里先說明C0代表后續的一個字節按照整數進行讀取,對應0x40(64),即代表是 Redis 的 64位架構。下面我們再來說明為什么會有以上的區別:

    其實代表值長度的不一定只有一個字節,這里會根據前兩位進行判斷(C0 對應1100 0000):

    • 如果前兩位是 00 ,那么后續的 6 位(可表示 0 ~ 63)就代表實際的字符串長度。

    • 如果前兩位是 01,那么接下來的一個字節也會用于表示長度,加上第一個剩下的 6 位,總共 14 位(可表示0 ~ 16383)代表實際的字符串長度。

    • 如果前兩位是 10,那么剩下 6 位的值如果是 0,就代表隨后的 32 字節代表具體長度,如果剩下 6 位的值是 1,就代表隨后的 64 字節代表具體長度。

    • 如果前兩位是 11,則需要根據整個字節的值再進行判斷,如果是C0就代表將隨后的 1 字節表示整數,如果是 C1 就代表隨后的 2 字節表示整數,如果是 C2 就代表隨后的 4 字節表示整數,如果是C3就代表隨后的內容是使用LZF 壓縮算法處理后的內容。

  3. ctime(文件創建時間)

    這部分對應FA 05 63 74 69 6D 65 C2 44 11 57 65,參考 1 可知開始的FA代表AUX05代表隨后的 5 字節是屬性名,即ctime。參考 2 中解析,可知隨后的C2代表后續的 4 字節即44 11 57 65表示整數,由于需要按照小端序讀取,因此對應的內容是 0x65571144,即秒級時間戳,如下圖所示:

    image-20231120085845280

  4. used-mem(內存使用大小)

    這部分對應FA 08 75 73 65 64 2D 6D 65 6D C2 40 15 12 00,參考 1 可知開始的FA代表AUX08代表隨后的 8 字節是屬性名,即used-mem。參考 3 ,可知隨后的C2代表后續的 4 字節即40 15 12 00表示整數,對應的內容是 0x00121540,即 Redis 在 創建 rdb 文件前占用的內存是 1185088 字節(1.13 MB)。

  5. aof-base (是否為 aof 基準文件)

    這部分對應FA 08 61 6F 66 2D 62 61 73 65 C0 00,參考 1 可知開始的FA代表AUX08代表隨后的 8 字節是屬性名,即aof-base。參考 2 中解析,可知隨后的C0代表后續的 1 字節即00表示整數,即該 RDB 文件不是作為 AOF 的基準文件,后文中可以看到在 AOF 中生成的 RDB 文件中該值為 1。

0x02 數據部分

FE 00 FB 01 00 00 05 68 65 6C 6C 6F 05 77 6F 72 6C 64,這部分開始對應具體的數據信息,先展示源碼:

/* save all databases, skip this if we're in functions-only mode */
if (!(req & SLAVE_REQ_RDB_EXCLUDE_DATA)) {for (j = 0; j < server.dbnum; j++) {if (rdbSaveDb(rdb, j, rdbflags, &key_counter) == -1) goto werr;}
}// 以下內容是 rdbSaveDb 函數內的語句/* Write the SELECT DB opcode */
if ((res = rdbSaveType(rdb,RDB_OPCODE_SELECTDB)) < 0) goto werr;
written += res;
if ((res = rdbSaveLen(rdb, dbid)) < 0) goto werr;
written += res;
/* Write the RESIZE DB opcode. */
unsigned long long expires_size = dbSize(db, DB_EXPIRES);
if ((res = rdbSaveType(rdb,RDB_OPCODE_RESIZEDB)) < 0) goto werr;
written += res;
if ((res = rdbSaveLen(rdb,db_size)) < 0) goto werr;
written += res;
if ((res = rdbSaveLen(rdb,expires_size)) < 0) goto werr;
written += res;

可以看出這部分是遍歷所有的數據庫內容然后進行保存,下面再結合具體的內容進行介紹。

首先是FE 00,其中FE(254)對應RDB_OPCODE_SELECTDB常量是查詢數據庫的標志,00即代表 0 號數據庫。

然后是FB 01 00,其中FB(251)對應RDB_OPCODE_RESIZEDB常量是查詢該數據庫大小的標志,根據if ((res = rdbSaveLen(rdb,db_size)) < 0) goto werr;知道01代表數據庫的大小,即只有一條數據,根據if ((res = rdbSaveLen(rdb,expires_size)) < 0) goto werr;知道00代表沒有包含過期標志的數據。

最后是00 05 68 65 6C 6C 6F 05 77 6F 72 6C 64,代表具體的數據內容。其中開始的00代表類型是字符串,參考源碼可知(RDB_TYPE_STRING 的值是 0):

/* Save the object type of object "o". */
int rdbSaveObjectType(rio *rdb, robj *o) {switch (o->type) {case OBJ_STRING:return rdbSaveType(rdb,RDB_TYPE_STRING);case OBJ_LIST:if (o->encoding == OBJ_ENCODING_QUICKLIST || o->encoding == OBJ_ENCODING_LISTPACK)return rdbSaveType(rdb, RDB_TYPE_LIST_QUICKLIST_2);elseserverPanic("Unknown list encoding");case OBJ_SET:if (o->encoding == OBJ_ENCODING_INTSET)return rdbSaveType(rdb,RDB_TYPE_SET_INTSET);else if (o->encoding == OBJ_ENCODING_HT)return rdbSaveType(rdb,RDB_TYPE_SET);else if (o->encoding == OBJ_ENCODING_LISTPACK)return rdbSaveType(rdb,RDB_TYPE_SET_LISTPACK);elseserverPanic("Unknown set encoding");case OBJ_ZSET:if (o->encoding == OBJ_ENCODING_LISTPACK)return rdbSaveType(rdb,RDB_TYPE_ZSET_LISTPACK);else if (o->encoding == OBJ_ENCODING_SKIPLIST)return rdbSaveType(rdb,RDB_TYPE_ZSET_2);elseserverPanic("Unknown sorted set encoding");case OBJ_HASH:if (o->encoding == OBJ_ENCODING_LISTPACK)return rdbSaveType(rdb,RDB_TYPE_HASH_LISTPACK);else if (o->encoding == OBJ_ENCODING_HT)return rdbSaveType(rdb,RDB_TYPE_HASH);elseserverPanic("Unknown hash encoding");case OBJ_STREAM:return rdbSaveType(rdb,RDB_TYPE_STREAM_LISTPACKS_3);case OBJ_MODULE:return rdbSaveType(rdb,RDB_TYPE_MODULE_2);default:serverPanic("Unknown object type");}return -1; /* avoid warning */
}

隨后的05 68 65 6C 6C 6F中的 05表示鍵的長度是5,對應68 65 6C 6C 6Fhello。最后的05 77 6F 72 6C 64代表值的長度也是 5,內容是77 6F 72 6C 64world

0x03 尾部信息

FF 18 7F 33 2E 0F C6 20 19,根據源碼#define RDB_OPCODE_EOF 255可知,FF(25)是文件的 EOF 即結束標志。隨后的 8 位根據源碼可知對應 CRC64 校驗碼:

/* EOF opcode */
if (rdbSaveType(rdb,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. */
cksum = rdb->cksum;
memrev64ifbe(&cksum);
if (rioWrite(rdb,&cksum,8) == 0) goto werr;

AOF

AOF 用于對數據庫的增量備份,如果需要開啟,需要將配置文件中的appendonly設置為 yes。同時,根據需要可以,設置appenddirname對應保存的文件夾,設置appendfilename用于配置文件名,設置appendfsync 用于配置頻率。開啟后,可以在指定的文件夾下看到類似以下的文件結構:

image-20231117142130770

其中 rdb 結尾的代表是 AOF 備份的基準文件,aof 文件是增量備份的執行命令信息,manifest 文件是記錄 aof 文件的元數據信息。

0x00 dump.aof.1.base.rdb

通過十六進制編輯器打開該文件,可以發現內容和 RDB 中的格式一致(創建數據前備份的,所以沒有數據部分):

在這里插入圖片描述

而由于是 AOF 的基準文件,這里aof-base的值是01即代表是基準文件。

0x01 dump.aof.1.incr.aof

文本文件,內容如下(*開頭代表命令包含的參數個數,$開頭代表命令的長度):

*2       // 兩個參數
$6       // 第一個參數長度為 6, 對應 SELECT 的長度
SELECT   
$1       // 第二個參數長度為 1, 對應 0, 即 0 號數據庫
0
*3       // 三個參數
$3       // 第一個參數長度為 3, 對應 set 的長度
set
$5       // 第二個參數長度為 5, 對應 hello 的長度
hello
$0       // 第三個參數長度為 0*3       // 三個參數
$3       // 第一個參數長度為 3, 對應 set 的長度
set
$5       // 第二個參數長度為 5, 對應 hello 的長度
hello
$5
world    // 第三個參數長度為 5, 對應 world 的長度

0x02 dump.aof.manifest

文本文件,內容如下:

file dump.aof.1.base.rdb seq 1 type b
file dump.aof.1.incr.aof seq 1 type i

其中seq 1 代表文件序號為 1,type b代表type base即基準文件,type i代表type increment即增量文件。

總結

本文根據一個簡單的 RDB 文件講解了 RDB 文件的存儲格式,同時也簡單介紹了 AOF 的文件格式。關于 RDB 中的 LZF 壓縮算法和更復雜數據的存儲方式(包含過期時間,數據類型為 Set,Map)等未作介紹,將留到下次。

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

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

相關文章

2014年5月28日 Go生態洞察:GopherCon 2014大會回顧

&#x1f337;&#x1f341; 博主貓頭虎&#xff08;&#x1f405;&#x1f43e;&#xff09;帶您 Go to New World?&#x1f341; &#x1f984; 博客首頁——&#x1f405;&#x1f43e;貓頭虎的博客&#x1f390; &#x1f433; 《面試題大全專欄》 &#x1f995; 文章圖文…

Java面試附答案:掌握關鍵技能,突破面試難題!

問題&#xff1a;什么是大O表示法&#xff1f;它在Java中的應用是什么&#xff1f; 回答&#xff1a; 大O表示法是一種用來衡量算法復雜度的方法&#xff0c;它描述了算法的時間復雜度和空間復雜度的增長速度。它使用符號O(n)來表示算法的漸進時間復雜度&#xff0c;其中n表示…

如何讓Python2與Python3共存

安裝 首先分別安裝Py2和Py3&#xff0c;我都安裝到C盤根目錄里了&#xff0c;然后分別將Py2和Py3都配置到系統環境變量中去&#xff1a;C:\Python36\Scripts\;C:\Python36\;C:\Python27\;C:\Python27\Scripts; 配置 修改兩個版本的可執行文件名字 驗證 重新配置一下pip …

Ubuntu刪除應用圖標

刪除用戶下的圖標 sudo nautilus ~/.local/share/applications刪除系統下的圖標 sudo nautilus /usr/share/applications

大數據-之LibrA數據庫系統告警處理(ALM-25500 KrbServer服務不可用)

告警解釋 系統按30秒周期性檢測組件KrbServer的服務狀態。當檢測到組件KrbServer服務異常時產生該告警。 當檢測到組件KrbServer服務恢復時告警恢復。 告警屬性 告警ID 告警級別 可自動清除 25500 致命 是 告警參數 參數名稱 參數含義 ServiceName 產生告警的服務…

解決MySQL中某列數據過長無法入庫的問題-Details:data too long for column `xxx` at row 1

問題描述&#xff1a; 我在將軌跡的經緯度轉換為字符串入庫時&#xff0c;遇到寫入問題 Mysql數據入庫報錯&#xff1a; Caused by:java.long.exception:寫入數據庫表失敗.Details:data too long for column xxx at row 1&#xff0c;我的xxx字段類型是string,在mysql庫表中…

加速CI構建,實現高效流水線——CloudBees CI發布工作區緩存功能

加速軟件交付流程能夠更快接觸到客戶&#xff0c;獲得競爭優勢。然而&#xff0c;識別這一過程中存在的瓶頸可能頗具挑戰。讓我們從審查構建和測試階段開始著手。例如&#xff0c;當CI作業執行時間較長時&#xff0c;它會延遲開發人員的反饋循環&#xff0c;從而可能導致發布延…

使用Python解析CAN總線

緣起 在新能源車輛的開發和維護中&#xff0c;經常需要對CAN總線數據進行分析。CANOE等總線軟件雖然方便&#xff0c;但功能有限&#xff0c;難以滿足數據分析的要求。Matlab的Vehicle Network Toolbox可以方便的進行數據解析和分析&#xff0c;它是閉源且收費的。因此&#x…

SpringBoot啟動順序

前言 每次有人問起SpringBoot的啟動順序是不是又來翻博客了&#xff1f;其實只需要稍微查看Spring的源碼即可 步驟 SpringBoot的入口org.springframework.boot.SpringApplication#run(String... args), 這里面實現了SpringBoot程序啟動的所有步驟 啟動事件的順序可以看監聽器…

uni-app 使用uni.getLocation獲取經緯度配合騰訊地圖api獲取當前地址

前言 最近在開發中需要根據經緯度獲取當前位置信息&#xff0c;傳遞給后端&#xff0c;用來回顯顯示當前位置 查閱uni-app文檔&#xff0c;發現uni.getLocation () 可以獲取到經緯度&#xff0c;但是在小程序環境沒有地址信息 思考怎么把經緯度換成地址&#xff0c;如果經緯度…

buildadmin+tp8表格操作(1)----表頭上方添加按鈕和自定義按鈕

buildAdmin 的表頭上添加一些按鈕&#xff0c;并實現功能 添加按鈕 <template><!-- buttons 屬性定義了 TableHeader 本身支持的頂部按鈕&#xff0c;僅需傳遞按鈕名即可 --><!-- 這里的框架自帶的 頂部按鈕 分別有 刷新 &#xff0c; 添加&#xff0c; 編輯&…

C++ 問題 怎么在C++11標準語法中調用C++20的類

一. 問題 在工作中,因為一個算法功能需要跟別的部門對接,他們提供了該算法的頭文件.h,靜態庫.lib,動態庫.dll。但是頭文件中使用了C++20才有的新特性,如#include等,而本地使用的vs2015開發環境,只支持C++11標準語法,這種情況下,該怎么把該算法集成到本地項目中呢? …

寫單元測試,沒你想得那么簡單!

前言 單元測試是什么我們就簡單介紹一下&#xff1a; 單元測試是針對程序模塊&#xff08;軟件設計的最小單位&#xff09;來進行正確性檢驗的測試工作。程序單元是應用的最小可測試部件。 接下來是本人對單元測試的理解和實踐。里面沒有廢話&#xff0c;希望每句話能說到你心…

YOLOv8改進實戰 | 更換主干網絡Backbone(六)之輕量化模型VanillaNet進階篇

前言 輕量化網絡設計是一種針對移動設備等資源受限環境的深度學習模型設計方法。下面是一些常見的輕量化網絡設計方法: 網絡剪枝:移除神經網絡中冗余的連接和參數,以達到模型壓縮和加速的目的。分組卷積:將卷積操作分解為若干個較小的卷積操作,并將它們分別作用于輸入的不…

每日一題(LeetCode)----鏈表--分隔鏈表

每日一題(LeetCode)----鏈表–分隔鏈表 1.題目&#xff08;86. 分隔鏈表&#xff09; 給你一個鏈表的頭節點 head 和一個特定值 x &#xff0c;請你對鏈表進行分隔&#xff0c;使得所有 小于 x 的節點都出現在 大于或等于 x 的節點之前。 你應當 保留 兩個分區中每個節點的初…

關于LORA的優勢以及常見應用產品領域

lora的優勢&#xff1a; 1 低功耗 2 傳輸距離遠 3 信號穿透性好 4 靈敏度高&#xff0c;適合可靠性要求高的領域 5 低成本 Lora 產品領域 &#xff1a; 一、智慧城市 1 智能停車&#xff1a;在較大的停車場&#xff0c;通過Lora技術&#xff0c;采集車位信息&#xff0…

問題解決:Ubuntu18.04下nvcc -V指令可用,/usr/local/下卻沒有cuda文件夾,原因分析及卸載方法

問題描述 今天要運行一個程序&#xff0c;需要CUDA版本高于10.0&#xff0c;我的電腦無法運行&#xff0c;于是開始檢查 首先使用nvidia-smi與nvcc -V指令 能夠看出來&#xff0c;當前顯卡驅動適合的CUDA版本為12.1&#xff0c;而本機安裝的版本是9.1.85&#xff0c;那么就需…

實驗7設計建模工具的使用(三)

二&#xff0c;實驗內容與步驟 1. 百度搜索1-2張狀態圖&#xff0c;請重新繪制它們&#xff0c;并回答以下問題&#xff1a; 1&#xff09;有哪些狀態&#xff1b; 2&#xff09;簡要描述該圖所表達的含義&#xff1b; 要求&#xff1a;所繪制的圖不得與本文中其它習題一樣…

有一臺電腦一部手機就可以在網上賺錢,這些項目你也可以學會

很多人都希望能夠在家中或者閑暇的時候&#xff0c;能夠在網上賺錢&#xff0c;而網絡給了我們這樣的可能。只要有一臺電腦和一部手機&#xff0c;你就可以開始你的賺錢之旅。這些項目并不難&#xff0c;只要你肯學&#xff0c;就一定能夠成功。 1、美工設計 這個副業主要是推薦…

【STL】string類(中)

目錄 1&#xff0c;rbegin 和 rend 2&#xff0c;reserve & capacity 3&#xff0c;max_size ( ) 4&#xff0c;size&#xff08;&#xff09;& resize 1&#xff0c;void resize (size_t&#xff0c;char c&#xff09; 5&#xff0c;push_back & append 1…