Redis 源碼簡潔剖析 13 - RDB 文件

RDB 是什麼

Redis *.rdb 是內存的二進制文件,通過 *.rdb 能夠完全回覆 Redis 的運行狀態。

![](http://yano.oss-cn-beijing.aliyuncs.com/blog/
20220218175013.png?x-oss-process=style/yano)

RDB 文件格式

詳細信息可參考:Redis RDB Dump File Format

RDB 文件的頭部佔用 9bytes,前 5bytes 爲 Magic String, 後 4bytes 爲版本號

52 45 44 49 53 #"REDIS", 就像 java 的 class 文件以 0xCAFEBABE 開頭一樣
30 30 30 36    #RDB 版本號,30 表示‘0’,版本號爲 0006=6

注意:版本號是字符串而不是整型:

snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);

RDB_VERSION 詳細信息可參考:Redis RDB Version History

Body

DB Selector

FE 開頭表示後跟表示 DB Selector,例如:

FE 00   #FE 表明數據庫的哪個 db,此處爲 db0

注意:DB Selector 長度不固定,具體的編碼方式請參見後文的 Length 編碼。

AUX Fields

FA 開頭表示後跟 AUX Fields, 記錄生成 Dump 文件的 Redis 相關信息,例如 redis-ver、redis-bits、used-mem、aof-preamble 和 repl-id 等。這些信息採用 String 編碼;

注意:redis3.0 版本的 RDB 版本號爲 6,redis3.2 的版本號爲 7;

Key-Value

key-value 有三種格式:

  1. expire 爲 second

    FD $unsigned int    #失效時間(秒),4 個字節
    $value-type         #1 個字節,表明數據類型:set,map 等
    $string-encoded-key #key 值,字符串類型
    $encoded-value      #value, 編碼方式和類型有關
    
  2. expire 爲 millisecond

    FC $unsigned long    #失效時間(毫秒),8 個字節
    $value-type          #數據類型,1 個字節
    $string-encoded-key  #key,字符串類型
    $encoded-value       #value, 編碼方式和類型有關
    
  3. 無 expire

    $value-type         #數據類型,1 個字節
    $string-encoded-key #key,字符串類型
    $encoded-value      #value, 編碼方式和類型有關
    
FF              #RDB 文件的結束
8byte checksum #循環冗餘校驗碼,Redis 採用 crc-64-jones 算法,初始值爲 0

編碼算法說明

Length 編碼

長度採用 BigEndian 格式存儲,爲無符號整數

  1. 如果以"00"開頭,那麼接下來的 6 個 bit 表示長度;
  2. 如果以“01”開頭,那麼接下來的 14 個 bit 表示長度;
  3. 如果以"10"開頭,該 byte 的剩餘 6bit 廢棄,接着讀入 4 個 bytes 表示長度 (BigEndian);
  4. 如果以"11"開頭,那麼接下來的 6 個 bit 表示特殊的編碼格式,一般用來存儲數字:
  • 0 表示用接下來的 1byte 表示長度
  • 1 表示用接下來的 2bytes 表示長度;
  • 2 表示用接下來的 4bytes 表示長度;

String 編碼

該編碼方式首先採用 Length 編碼 進行解析:

  1. 從上面的Length 編碼知道,如果以"00","01","10"開頭,首先讀取長度;然後從接下來的內容中讀取指定長度的字符;
  2. 如果以"11"開頭,而且接下來的 6 個字節爲“0”、“1”和“2”, 那麼直接讀取接下來的 1,2,4bytes 做爲字符串的內容(實際上存儲的是數字,只不過按照字符串的格式存儲);
  3. 如果以“11”開頭,而且接下來的 6 個字節爲"3", 表明採用 LZF 壓縮字符串格式:

LZF 編碼的解析步驟爲:

  1. 首先採用Length 編碼讀取壓縮後字符串的長度 clen;
  2. 接着採用Length 編碼讀取壓縮前的字符串長度;
  3. 讀取 clen 長度的字節,並採用 lzf 算法解壓得到原始的字符串

Score 編碼

  1. 讀取 1 個字節,如果爲 255,則返回負無窮;
  2. 如果爲 254,返回正無窮;
  3. 如果爲 253,返回非數字;
  4. 否則,將該字節的值做爲長度,讀取該長度的字節,將結果做爲分值;

Value 編碼

Redis 中的 value 編碼包括如下類型:

其中 String 編碼在前面已經介紹過,接下來逐一介紹其他的 9 種編碼方式;

List

  1. 首先用 Length 編碼讀取 List 的長度 lsize;
  2. 採用 String 編碼讀取 lsize 個字符串

Set

同 List

Sorted Set

  1. 首先用 Length 編碼讀取 Sorted Set 的長度 zsize;
  2. 採用 String 編碼讀取字符串,採用 Score 編碼讀取分值;
  3. 循環讀取 zsize 次;

Hash

  1. 採用 Length 編碼讀取 Hash 的大小 hsize;
  2. 採用 String 編碼讀取 2*hsize 的字符串,按照 key,value 的方式組裝成 Map

Zipmap

用於存儲 hashmap,Redis2.6 之後,該編碼被廢棄,轉而採用 Ziplist 編碼;

採用 String 編碼讀取整個 zipmap 字符串,hashmap 字符串的格式爲:

<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"<zmend>
  1. zmlen: 一個字節,Zipmap 的大小;如果>=254, 意味着 zipmap 的大小無法直接獲取到,必須要遍歷整個 zipmap 才能得到大小;
  2. len: 字符串長度,1 或 5 個字節長度;如果第一個字節在 0~252 之間,那麼長度爲第一個字節;如果爲 253, 那麼接下來的 4 個字節表示長度;254 和 255 是無效值;
  3. free:1 字節,表明 value 空閒的字節數;
  4. zmend:0xff, 表示 Zipmap 的結尾;

Ziplist

採用 String 編碼讀取整個 ziplist 字符串,字符串的格式爲:

<zlbytes><zltail><zllen><entry><entry><zlend>
  1. zlbytes:4 字節無符號整數,表示 ziplist 佔用的總字節數;
  2. zltail:4 字節無符號整數 (little endian), 表示尾元素的偏移量;
  3. zllen:2 字節無符號整數 (little endian), 表示 ziplist 中的元素個數,當元素個數大於 65535 時,無法用 2 字節表示,需要遍歷列表獲取元素個數;
  4. entry:ziplist 中的元素;
  5. zlend: 常量 (0xff), 表示 ziplist 的結尾;

entry 的格式:

<length-prev-entry><encoding><content>
  1. lenth-prev-entry: 如果第一個字節<254, 則用 1bytes 表示長度;否則則用接下來的 4bytes(無符號整數)表示長度;
  2. encoding
  • "00"開頭:字符串,用接下來的 6bit 表示長度;
  • "01"開頭:字符串,用接下來的 14bit 表示長度;
  • "10"開頭:字符串,忽略本字節的 6bit, 用接下來的 32bit 表示長度;
  • "11000000"開頭:整數,內容爲接下來的 16bit;
  • "11010000"開頭:整數,內容爲接下來的 32bit;
  • "11100000"開頭:整數,內容爲接下來的 64bit;
  • "11110000"開頭:整數,內容爲接下來的 24bit;
  • "11111110"開頭:整數,內容爲接下來的 8bit;
  • "1111"開頭 :整數,內容爲接下來的 4bit 的值減去 1;
  1. content
    entry 內容,它的長度通過前面的 encoding 確定;

注意:元素長度、內容長度等都是採用 Little Endian 編碼;

Intset

Intset 是一個整數組成的二叉樹;當 set 的所有元素都是整形的時候,Redis 會採用該編碼進行存儲;Inset 最大可以支持 64bit 的整數,做爲優化,如果整數可以用更少的字節數表示,Redis 可能會用 16~32bit 來表示;注意的是當插入一個長度不一樣的整數時,有可能會引起整個存儲結構的變化;

由於 Intset 是一個二叉樹,因此它的元素都是排序過的;
採用 String 編碼讀取整個 intset 字符串,字符串的格式爲:

<encoding><length-of-contents><contents>
  1. encoding:32bit 的無符號整數;可選值包括 2、4 和 8;表示 inset 中的每個整數佔用的字節數;
  2. length-of-contents:32bit 無符號整數,表示 Intset 中包含的整數個數;
  3. contents: 整數數組,長度由 length-of-contents 決定;

Sorted Set in Ziplist Encoding

採用 Ziplist 編碼,區別在於用兩個 entry 分別表示元素和分值;

Hashmap in Ziplist Encoding

採用 Ziplist 編碼,區別在於用兩個 entry 分別表示 key 和 value;

實際例子

本篇文章在本地安裝並啓動 Redis 服務,保存一個 string 類型的字符串,save 之後查看保存的 rdb 文件的二進制。

安裝、啓動 Redis

下載見:Redis Download

啓動 Redis server:

src/redis-server&

啓動一個 Redis client:

src/redis-cli

保存字符串

127.0.0.1:6379> set name yano
OK

保存 RDB 文件

127.0.0.1:6379> save
80277:M 15 Feb 2022 10:51:07.308 * DB saved on disk
OK

在剛執行 redis-cli 的目錄下,就生成了 rdb 文件,文件名是 dump.rdb。

分析 RDB 文件

使用 hexedit 命令分析 dump.rdb 文件:

hexedit dump.rdb

dump.rdb 文件內容如下:

本篇文章只是分析 rdb 文件的基本結構和格式,只保存了一個最基礎的 string。(圖畫了一個小時😁)RDB 這塊的 Redis 源碼就不分析了,基本上都是按照這個結構來的。

參考鏈接

Redis 源碼簡潔剖析系列

最簡潔的 Redis 源碼剖析系列文章

Java 編程思想-最全思維導圖-GitHub 下載鏈接,需要的小夥伴可以自取~

原創不易,希望大家轉載時請先聯繫我,並標註原文鏈接。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章