可靠性保證模組,瞭解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建立子程序的過程:
1。3 rdbSaveToSlavesSockets
只在主從複製時呼叫,Redis Server在採用不落盤方式傳輸RDB檔案進行主從複製時,建立RDB檔案。
會被startBgsaveForReplication呼叫,被如下函式呼叫:
syncCommandRedis server執行主從複製命令
replicationCronRedis server週期性檢測主從複製狀態時觸發RDB生成
也是透過fork建立子程序,讓子程序生成RDB。但rdbSaveToSlavesSockets是透過網路
以位元組流直接傳送RDB檔案的二進位制資料=》從節點
。
為使從節點夠識別用來同步資料的RDB內容,rdbSaveToSlavesSockets呼叫rdbSaveRioWithEOFMark,在RDB二進位制資料的前後加上標識:
那RDB檔案建立的三個時機,也就分別是:
save命令執行
bgsave命令執行
主從複製
還有其它地方會觸發時機嗎?透過在Redis原始碼中查詢
rdbSave、rdbSaveBackground
,就能知道:
rdbSave還會在:flushallCommand函式被呼叫,執行flushall命令prepareForShutdown函式中被呼叫,即正常關閉時
rdbSaveBackground:當主從複製採用落盤檔案方式傳輸RDB時,也會被startBgsaveForReplication呼叫Redis server執行時的週期性執行函式serverCron也會呼叫rdbSaveBackground
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字元。
即在剛才生成的RDB檔案中,如想轉換成ASCII字元,檔案頭內容其實就已包含REDIS的字串和一些數字。
3 生成檔案頭
RDB檔案頭的內容首先是
魔數
,記錄了RDB檔案版本。
rdbSaveRio中,魔數透過snprintf生成:字串“REDIS”+RDB版本的宏定義RDB_VERSION(9)。
然後,rdbSaveRio會呼叫rdbWriteRaw,將魔數寫入RDB檔案:
rdbWriteRaw函式
會呼叫rioWrite完成寫入。rioWrite是RDB檔案內容的最終寫入函式,根據要寫入資料長度,把待寫入緩衝區中的內容寫入RDB。RDB檔案生成過程中,會有不同函式負責寫入不同部分的內容,不過這些函式最終都還是呼叫rioWrite完成資料的實際寫入。
接著rdbSaveRio呼叫rdbSaveInfoAuxFields,將和Redis server相關的一些屬性資訊寫入RDB檔案頭:
rdbSaveInfoAuxFields使用KV對形式,在RDB檔案頭中記錄Redis server屬性資訊。RDB檔案頭記錄的一些主要資訊及對應K和V:
rdbSaveAuxFieldStrStr/rdbSaveAuxFieldStrInt都會呼叫rdbSaveAuxField寫入屬性值,分三步完成一個屬性資訊的寫入:
呼叫rdbSaveType寫入一個操作碼用來在RDB檔案中標識接下來的內容是啥。當寫入屬性資訊,該操作碼即RDB_OPCODE_AUX(250),對應十六進位制FA。便於解析RDB檔案。如讀取RDB檔案時,若程式讀到FA,表明接下來的內容是屬性資訊。
RDB檔案使用多個操作碼標識檔案中不同內容:
rdbSaveAuxField呼叫rdbSaveRawString寫入屬性資訊的K,K通常是個字串。rdbSaveRawString是寫入字串的通用函式:先記錄字串長度(解析RDB檔案時,程式可知道當前讀取的字串應該讀取多少個位元組),再記錄實際字串。
為節省RDB檔案所佔空間,若字串記錄的是個整數,rdbSaveRawString會呼叫rdbTryIntegerEncoding,嘗試用
緊湊結構
對字串編碼
rdbSaveRawString執行邏輯,它呼叫rdbSaveLen寫入字串長度,rdbWriteRaw寫入實際資料
rdbSaveAuxField就需寫入屬性資訊的V。因屬性資訊的V通常也是字串,所以類似step2寫入屬性資訊的K,rdbSaveAuxField會呼叫rdbSaveRawString寫入屬性資訊的V。
rdbSaveAuxField執行過程:
至此,RDB檔案頭寫完。
於是,rdbSaveRio開始寫入實際的KV對。
4 生成檔案資料
因為Redis server上的KV對可能被儲存在不同DB,所以,
rdbSaveRio會執行一個迴圈,遍歷每個DB,將其中的KV對寫入RDB
。
這迴圈流程,rdbSaveRio先將
SELECTDB操作碼
和對應資料庫編號寫入RDB,程式在解析RDB時,就知道接下來的KV所屬DB:
接著,rdbSaveRio會寫入
RESIZEDB操作碼
,用標識全域性雜湊表和過期key雜湊表中KV對數量的記錄:
RESIZEDB操作碼後,緊接著記錄的是全域性雜湊表中的KV對,數量是2,然後是過期key雜湊表中的鍵值對,數量為0。剛才在生成RDB檔案前,只插入了兩個鍵值對,所以,RDB檔案中記錄的資訊和我們剛才的操作結果是一致。
記錄完這些資訊後,rdbSaveRio接著
執行一個迴圈流程
:取出當前資料庫中的每個KV對,並呼叫rdbSaveKeyValuePair,將它寫入RDB:
rdbSaveKeyValuePair負責將KV對實際寫入RDB檔案。先將KV對的TTL、LRU空閒時間或LFU訪問頻率寫入RDB。寫這些資訊時,都先呼叫rdbSaveType,寫入標識這些資訊的操作碼。
至此,rdbSaveKeyValuePair就要開始實際寫入KV對:
為便於解析RDB時恢復KV對,rdbSaveKeyValuePair先呼叫rdbSaveObjectType,寫入鍵值對的型別標識
然後呼叫rdbSaveStringObject寫入KV對的K
最後,呼叫rdbSaveObject寫入KV對的V
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檔案中對應的記錄內容,畫在下圖:
可見該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寫入檢驗值
生成的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檔案內容。