華為大佬詳解Redis記憶體快照檔案RDB的生成過程

可靠性保證模組,瞭解Redis資料持久化的實現,其中包括Redis記憶體快照RDB檔案的生成方法,以及AOF日誌的記錄與重寫。掌握RDB檔案的格式,學習到如何製作資料庫映象,並且你也會進一步掌握AOF日誌重寫對Redis效能影響。

主從複製是分散式資料系統保證可靠性的一個重要機制,而Redis就給我們提供了非常經典的實現,所以透過學習這部分內容,你就可以掌握到在資料同步實現過程中的一些關鍵操作。

1 RDB建立的入口函式

建立RDB檔案的函式如下:

1。1 rdbSave

在本地磁碟建立RDB檔案。對應save命令,在實現函式saveCommand中被呼叫。rdbSave最終會呼叫rdbSaveRio實際建立RDB檔案。rdbSaveRio執行邏輯就體現了RDB檔案的格式和生成過程。

1。2 rdbSaveBackground

使用後臺子程序方式,在本地磁碟建立RDB檔案。對應bgsave命令,在bgsaveCommand中被呼叫。

呼叫fork建立一個子程序,讓子程序呼叫rdbSave繼續建立RDB檔案,而父程序,即主執行緒本身可繼續處理客戶端請求。

rdbSaveBackground建立子程序的過程:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

1。3 rdbSaveToSlavesSockets

只在主從複製時呼叫,Redis Server在採用不落盤方式傳輸RDB檔案進行主從複製時,建立RDB檔案。

會被startBgsaveForReplication呼叫,被如下函式呼叫:

syncCommandRedis server執行主從複製命令

replicationCronRedis server週期性檢測主從複製狀態時觸發RDB生成

也是透過fork建立子程序,讓子程序生成RDB。但rdbSaveToSlavesSockets是透過網路

以位元組流直接傳送RDB檔案的二進位制資料=》從節點

為使從節點夠識別用來同步資料的RDB內容,rdbSaveToSlavesSockets呼叫rdbSaveRioWithEOFMark,在RDB二進位制資料的前後加上標識:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

那RDB檔案建立的三個時機,也就分別是:

save命令執行

bgsave命令執行

主從複製

還有其它地方會觸發時機嗎?透過在Redis原始碼中查詢

rdbSave、rdbSaveBackground

,就能知道:

rdbSave還會在:flushallCommand函式被呼叫,執行flushall命令prepareForShutdown函式中被呼叫,即正常關閉時

rdbSaveBackground:當主從複製採用落盤檔案方式傳輸RDB時,也會被startBgsaveForReplication呼叫Redis server執行時的週期性執行函式serverCron也會呼叫rdbSaveBackground

Redis原始碼中建立RDB檔案的函式呼叫關係:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

最終生成RDB檔案的函式其實是rdbSaveRio。

2 RDB的組成

一個RDB檔案主要是由如下部分組成:

檔案頭

:儲存Redis的魔數、RDB版本、Redis版本、RDB檔案建立時間、鍵值對佔用的記憶體大小等資訊

檔案資料部分

:儲存Redis資料庫實際的所有鍵值對

檔案尾

:儲存RDB檔案的結束識別符號及整個檔案的校驗值。該校驗值用來在Redis server載入RDB檔案後,檢查檔案是否被篡改

準備一個RDB檔案。

第一步,在Redis目錄下,啟動一個用來測試的Redis server:

。/redis-server

第二步,執行flushall,清空當前資料庫:

。/redis-cli flushall

第三步,使用redis-cli登入剛啟動的Redis server,執行set命令插入一個String型別的鍵值對,再執行hmset命令插入一個Hash型別的鍵值對。執行save命令,將當前資料庫內容儲存到RDB:

127。0。0。1:6379>set hello redis OK 127。0。0。1:6379>hmset userinfo uid 1 name zs age 32 OK 127。0。0。1:6379> save OK

在剛才執行redis-cli命令的目錄下,找見剛生成的RDB檔案,檔名應是dump。rdb。

因RDB檔案實際是個二進位制資料組成的檔案,所以使用一般文字編輯軟體開啟RDB,都是亂碼。如想檢視RDB檔案中二進位制資料和對應的ASCII字元,可使用

Linux上的od命令

,可用不同進位制展示資料,並顯示對應ASCII字元。

如執行如下的命令,讀取dump。rdb檔案,並用十六進位制展示檔案內容,同時檔案中每個位元組對應的ASCII字元也會被對應顯示出來。

od -A x -t x1c -v dump。rdb

以下程式碼展示的就是我用od命令,檢視剛才生成的dump。rdb檔案後,輸出的從檔案頭開始的部分內容。你可以看到這四行結果中,第一和第三行是用十六進位制顯示的dump。rdb檔案的位元組內容,這裡每兩個十六進位制數對應了一個位元組。而第二和第四行是od命令生成的每個位元組所對應的ASCII字元。

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

即在剛才生成的RDB檔案中,如想轉換成ASCII字元,檔案頭內容其實就已包含REDIS的字串和一些數字。

3 生成檔案頭

RDB檔案頭的內容首先是

魔數

,記錄了RDB檔案版本。

rdbSaveRio中,魔數透過snprintf生成:字串“REDIS”+RDB版本的宏定義RDB_VERSION(9)。

然後,rdbSaveRio會呼叫rdbWriteRaw,將魔數寫入RDB檔案:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

rdbWriteRaw函式

會呼叫rioWrite完成寫入。rioWrite是RDB檔案內容的最終寫入函式,根據要寫入資料長度,把待寫入緩衝區中的內容寫入RDB。RDB檔案生成過程中,會有不同函式負責寫入不同部分的內容,不過這些函式最終都還是呼叫rioWrite完成資料的實際寫入。

接著rdbSaveRio呼叫rdbSaveInfoAuxFields,將和Redis server相關的一些屬性資訊寫入RDB檔案頭:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

rdbSaveInfoAuxFields使用KV對形式,在RDB檔案頭中記錄Redis server屬性資訊。RDB檔案頭記錄的一些主要資訊及對應K和V:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

rdbSaveAuxFieldStrStr/rdbSaveAuxFieldStrInt都會呼叫rdbSaveAuxField寫入屬性值,分三步完成一個屬性資訊的寫入:

呼叫rdbSaveType寫入一個操作碼用來在RDB檔案中標識接下來的內容是啥。當寫入屬性資訊,該操作碼即RDB_OPCODE_AUX(250),對應十六進位制FA。便於解析RDB檔案。如讀取RDB檔案時,若程式讀到FA,表明接下來的內容是屬性資訊。

RDB檔案使用多個操作碼標識檔案中不同內容:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

rdbSaveAuxField呼叫rdbSaveRawString寫入屬性資訊的K,K通常是個字串。rdbSaveRawString是寫入字串的通用函式:先記錄字串長度(解析RDB檔案時,程式可知道當前讀取的字串應該讀取多少個位元組),再記錄實際字串。

為節省RDB檔案所佔空間,若字串記錄的是個整數,rdbSaveRawString會呼叫rdbTryIntegerEncoding,嘗試用

緊湊結構

對字串編碼

rdbSaveRawString執行邏輯,它呼叫rdbSaveLen寫入字串長度,rdbWriteRaw寫入實際資料

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

rdbSaveAuxField就需寫入屬性資訊的V。因屬性資訊的V通常也是字串,所以類似step2寫入屬性資訊的K,rdbSaveAuxField會呼叫rdbSaveRawString寫入屬性資訊的V。

rdbSaveAuxField執行過程:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

至此,RDB檔案頭寫完。

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

於是,rdbSaveRio開始寫入實際的KV對。

4 生成檔案資料

因為Redis server上的KV對可能被儲存在不同DB,所以,

rdbSaveRio會執行一個迴圈,遍歷每個DB,將其中的KV對寫入RDB

這迴圈流程,rdbSaveRio先將

SELECTDB操作碼

和對應資料庫編號寫入RDB,程式在解析RDB時,就知道接下來的KV所屬DB:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

接著,rdbSaveRio會寫入

RESIZEDB操作碼

,用標識全域性雜湊表和過期key雜湊表中KV對數量的記錄:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

RESIZEDB操作碼後,緊接著記錄的是全域性雜湊表中的KV對,數量是2,然後是過期key雜湊表中的鍵值對,數量為0。剛才在生成RDB檔案前,只插入了兩個鍵值對,所以,RDB檔案中記錄的資訊和我們剛才的操作結果是一致。

記錄完這些資訊後,rdbSaveRio接著

執行一個迴圈流程

:取出當前資料庫中的每個KV對,並呼叫rdbSaveKeyValuePair,將它寫入RDB:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

rdbSaveKeyValuePair負責將KV對實際寫入RDB檔案。先將KV對的TTL、LRU空閒時間或LFU訪問頻率寫入RDB。寫這些資訊時,都先呼叫rdbSaveType,寫入標識這些資訊的操作碼。

至此,rdbSaveKeyValuePair就要開始實際寫入KV對:

為便於解析RDB時恢復KV對,rdbSaveKeyValuePair先呼叫rdbSaveObjectType,寫入鍵值對的型別標識

然後呼叫rdbSaveStringObject寫入KV對的K

最後,呼叫rdbSaveObject寫入KV對的V

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

rdbSaveObjectType會根據KV對的V型別,決定寫入到RDB中的KV對的型別標識。如建立RDB檔案前,寫入的KV對分別是String型別、Hash型別,而Hash型別因包含元素個數不多,所以採用ziplist儲存。這倆型別標識對應數值如下:

#define RDB_TYPE_STRING 0 #define RDB_TYPE_HASH_ZIPLIST 13

把剛才寫入的String型別鍵值對“hello”“redis”在RDB檔案中對應的記錄內容,畫在下圖:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

可見該KV對的開頭型別標識是0,和RDB_TYPE_STRING值一致。緊接著的key和value,都先記錄長度資訊,再記錄實際內容。

因為鍵值對的key都是String型別,所以rdbSaveKeyValuePair就用rdbSaveStringObject寫入。而KV對的V有不同型別,所以,rdbSaveObject根據V型別,將V底層資料結構中的內容寫入RDB。

除了鍵值對型別、鍵值對的key和value會被記錄以外,鍵值對的過期時間、LRU空閒時間或是LFU訪問頻率也都會記錄到RDB檔案中。這就生成了RDB檔案的資料部分。

5 生成檔案尾

當所有KV對都寫入RDB,rdbSaveRio就可開始寫入檔案尾內容:

RDB檔案結束的操作碼標識呼叫rdbSaveType,寫入檔案結束操作碼RDB_OPCODE_EOF

RDB檔案的校驗值呼叫rioWrite寫入檢驗值

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

生成的RDB檔案的檔案尾:

華為大佬詳解Redis記憶體快照檔案RDB的生成過程

6 總結

本文詳解了Redis記憶體快照檔案RDB的生成。建立RDB三個入口函式:

rdbSave

rdbSaveBackground

rdbSaveToSlavesSockets

它們在Redis原始碼中被呼叫的地方,就是觸發RDB檔案生成的時機。

關注RDB檔案的基本組成,並結合rdbSaveRio函式的執行流程,掌握RDB檔案頭、檔案資料部分和檔案尾這三個部分的生成:

RDB檔案使用多種操作碼來標識Redis不同的屬性資訊,以及使用型別碼來標識不同value型別

RDB檔案內容是自包含的,也就是說,無論是屬性資訊還是鍵值對,RDB檔案都會按照型別、長度、實際資料的格式來記錄,這樣方便程式對RDB檔案的解析

RDB檔案包含了Redis資料庫某一時刻的所有KV對及這些KV對的型別、大小、過期時間等資訊。瞭解RDB檔案格式和生成方法,就能開發解析RDB檔案的程式或是載入RDB檔案的程式。

如可在RDB檔案中查詢記憶體空間消耗大的鍵值對,即最佳化Redis效能時通常需要查詢的bigkey;也可分析不同型別鍵值對的數量、空間佔用等分佈情況,瞭解業務資料特點;還可自行載入RDB檔案,測試或排障。

可看redis-rdb-tools,幫助你分析RDB檔案內容。