redis 6源碼解析之 ziplist

ziplist

ziplist結構

ziplist的佈局以下,全部的字符默認使用小端序保存:git

+--------+--------+--------+--------+-------+-------+-------+
|zlbytes | zltail |  zllen | entry  |  ...  | entry | zlend |
+--------+--------+--------+--------+-------+-------+-------+
  • uint32_t zlbytes:爲一個無符號整數。保存了ziplist佔用的字節數,包含zlbytes字段自己佔用的4個字節。主要用於調整數據結構的大小。github

  • uint32_t zltail:最後一個entry的字節偏移量(非zlend)。用於從list的另外一端執行pop操做(即倒序遍歷)redis

  • uint16_t zllen:entry的數目。當保存的entry大於216-2個entry時,則將該值設置爲216-1,此時須要遍歷整個entry list來計算list中的entry數目數據結構

  • uint8_t zlend:表示ziplist中的最後一個entry。字節編碼等同於255(即FF)。表示ziplist的結束符佈局

ziplist中的每一個entry都使用一個元數據做爲前綴,該元數據包含兩部分的信息:首先保存了前一個entry的長度,用於倒序查找;再者保存了entry的編碼類型,表示entry的類型,如整數或字符串,當編碼類型爲字符串時,該字段也表示了字符串的長度。字符串的entry-data的長度就等同於該字符串的長度,而整數的entry-data的長度須要根據編碼類型進行判斷,並不必定等同於其entry-data字符串的長度(見下文encoding)。一個完整的entry爲:ui

+--------+--------+----------+
|prevlen |encoding|entry-data| 
+--------+--------+----------+

有時編碼類型即表示entry自己(例如小的整數),這種狀況下會忽略entry-data字段,此時entry變爲:編碼

+--------+--------+
|prevlen |encoding|
+--------+--------+

prevlen

prevlen表示前一個entry的長度,使用以下方式進行編碼:當前一個entry的長度小於254(255是個特殊字符,被zlend使用)字節時,該字段會使用一個字節(即8 bit)表示長度;當長度大於或等於254時,將會使用5個字節,此時第一個字節會被設置爲254(FE)來表示一個較大的數值,後續4個字節表示前面一個entry的長度。code

所以,prevlen的編碼爲:ip

  • 若是前一個entry的長度小於254,編碼爲:字符串

    +-------+--------+-----+
    |prevlen|encoding|entry| 
    +-------+--------+-----+
  • 若是前一個entry的長度大於254,編碼以下:

    +----+---------------+--------+-----+
    |0xFE|4 bytes prevlen|encoding|entry| 
    +----+---------------+--------+-----+

encoding

entryencoding字段取決於entry的內容。當entry爲字符串時,encoding的第一個字節的前2bit保存了編碼類型,剩餘的bit位表示字符串的長度。當entry爲整數時,encoding僅佔用1個字節,encoding的前2bit都設置爲1,後續的2bit用於指定整數的類型,如int16_t,int32_t。encoding中的第一個字節老是用於斷定entry的類型。舉例以下:

* |00pppppp| - 1 byte
 *       字符串的長度小於或等於63字節(6 bits).
 *      "pppppp" 表示6bit長度的無符號整數.
 * |01pppppp|qqqqqqqq| - 2 bytes
 *       字符串的長度小於或等於16383字節(14 bits).
 *       IMPORTANT: 14 bit的數字使用大端序保存.
 * |10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
 *      字符串的長度大於或等於16384字節,只使用第1個字節以後的4個字節表示長度,最大爲32^2-1,第一個
 *      字節的低6位沒有使用,設置爲0。所以entry的最大長度爲32
 *      IMPORTANT: 32 bit的數字使用大端序保存.
 * |11000000| - 3 bytes
 *      整數編碼爲int16_t (2 bytes).
 * |11010000| - 5 bytes
 *      整數編碼爲int32_t (4 bytes).
 * |11100000| - 9 bytes
 *      I整數編碼爲int64_t (8 bytes).
 * |11110000| - 4 bytes
 *      編碼爲24 bit的有符號整數 (3 bytes).
 * |11111110| - 2 bytes
 *      編碼爲8 bit的有符號整數 (1 byte).
 * |1111xxxx| - (xxxx  取值爲 0000 到 1101) 表示4bit的整數
 *      無符號整數的取值爲0到12,因爲沒法使用0000(被|11110000|編碼佔用)和1111(被zlend佔用),所以取值
 *      爲1到13,所以須要從低4位的整數減去1得到entry的值.
 * |11111111| - 表示ziplist的終止entry,即zlend

舉例

整數編碼

以下ziplist包含2個元素,表示字符串"2"和"5",長度爲15字節,能夠看到因爲數值小於13,其編碼和數值放在了一個字節中。

[0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
       |             |          |       |       |     |
    zlbytes        zltail    entries   "2"     "5"   end

前4個字節(zlbytes)表示15,即整個ziplist包含的字節數;第2個4字節(zltail)最後一個entry的字節偏移,即字符串爲"5"的entry的位置,偏移量爲12字節;接下來的16bit(entries)表示ziplist中的entry的數目,爲2;"00 f3"表示list中的第一個entry "2",它包含了前一個entry的長度(prevlen),爲0,"f3"對應的編碼爲"|1111xxxx|","xxxx"的取值爲0001到1101,去除前4個bit "1111",並減去1,獲得entry的值爲2。下一個entry的prevlen爲2,表示前一個entry佔用了2字節."f6"的編碼與前一個相同,去除前4個bit,並減去1,獲得entry的值爲5;最後的"ff"表示ziplist的結束(zlend)。

字符串編碼

在上述ziplist中追加一個"Hello World"的entry的編碼。第一個字節表示前面entry的長度,第二個字節表示encoding,二進制爲"|00pppppp|",所以"0b"表示一個11字節的字符串。從第3個字節(48)到最後一個字節(64)表示ASCII編碼的字符串"Hello World"。

[02] [0b] [48 65 6c 6c 6f 20 57 6f 72 6c 64]

源碼解析參見:ziplist.c

相關文章
相關標籤/搜索