【最完整系列】Redis-結構篇-壓縮列表

什麼是壓縮列表

壓縮列表 ziplist 在 redis 中的應用也很是普遍,它是咱們經常使用的 zset ,list 和 hash 結構的底層實現之一。當咱們的容器對象的元素個數小於必定條件時,redis 會使用 ziplist 的方式儲存,來減小內存的使用。redis

> hset test_hash me sidfate
    (integer) 1
    > object encoding test_hash
    "ziplist"
複製代碼

爲何要在元素較少的時候使用 ziplist ?shell

由於 redis 中的集合容器中,不少狀況都用到了鏈表的實現,元素和元素之間經過儲存的關聯指針有序的串聯起來,可是這樣的指針每每是 隨機I/O,也就是指針地址是不連續的(分佈不均勻)。而咱們的 ziplist 它自己是一塊連續的內存塊,因此它的讀寫是 順序I/O,從底層的磁盤讀寫來講,順序I/O 的效率確定是高於 隨機I/O 。你可能會問了,那爲何不都用 順序I/O 的 ziplist 代替 隨機I/O 呢,由於 ziplist 是連續內存,當你元素數量多了,意味着當你建立和擴展的時候須要操做更多的內存,因此 ziplist 針對元素少的時候才能提高效率。數組

ziplist 如何減小內存使用的呢?佈局

接下來讓咱們從源碼中一探究竟。編碼

源碼結構

題外話:每當你想要去探究一個項目的源碼的時候,首先應該去看的就是它的註釋,好的註釋便是文檔。同時也告訴咱們平時開始也要注意註釋的編寫。spa

首先從源碼的註釋中咱們能夠了解一些基礎信息:指針

ziplist 是通過特殊編碼的雙向列表結構,用來提升內存使用效率。它能夠儲存字符串或者整數值,其中整數值被編碼成實際的整數,而不是字符串形式。它能夠在 O(1) 時間內對列表的兩端進行 push 和 pop 操做。可是,由於每一個操做都須要從新分配 ziplist 使用的內存,因此實際的複雜度與 ziplist 使用的內存大小有關。code

ziplist 結構的佈局以下:cdn

<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
複製代碼

屬性 字節數 含義
zlbytes 4 壓縮列表佔用的內存字節數:在對壓縮列表進行內存重分配, 或者計算 zlend 的位置時使用。
zltail 4 壓縮列表表尾節點的偏移量:用來倒序遍歷壓縮列表。
zllen 2 記錄了壓縮列表包含的節點數量: 當這個屬性的值小於 UINT16_MAX (65535)時, 這個屬性的值就是壓縮列表包含節點的數量; 當這個值等於 UINT16_MAX 時, 節點的真實數量須要遍歷整個壓縮列表才能計算得出。
entry[] 待定 節點數組,包含元素的具體信息
zlend 1 特殊值 0xFF (十進制 255 ),用於標記壓縮列表的末端。

ziplist中的每一個節點 entry 的結構以下:對象

<prevlen> <encoding> <entry-data>
複製代碼

redis 爲了節約內存在 ziplist 的 entry 這個結構上有不少騷操做,讓我來一一說明。

prevlen

prevlen 表示前一個元素的長度,以便可以從後向前遍歷列表。它有一套特別的編碼方式:若是這個長度小於254字節,那麼它佔用1個字節;當長度大於或等於254時,佔用5個字節,第一個字節被設置爲254 (0xFE),其他的4個字節採用前一個條目的長度做爲值。prevlen 用 5 bytes表示時,不表明長度必定大於等於254,這是爲了減小 realloc 和 memmove 提升效率。

爲何臨界值是 254 ?咱們來算一筆,一個字節最大能儲存值爲255,那臨界值應該是255啊,別忘了咱們還有個 zlend,它的值是0xFF(255),爲了不混淆,因此用254區分。

encoding

encoding 表示元素的編碼,它取決於元素的內容。當元素是一個字符串時,編碼的第一個字節的前2位將保存用於存儲字符串長度的編碼類型,而後是字符串的實際長度。當條目是整數時,前2位都設置爲1。下面的2位用於指定在這個報頭以後將存儲哪一種類型的整數。對不一樣類型和編碼的概述以下。第一個字節老是足以肯定條目的類型。

  • |00pppppp| - 1 字節

    長度小於或等於63字節的字符串,63能夠用6個字節表示,因此 pppppp 表示字符串的實際長度。

  • |01pppppp|qqqqqqqq| - 2 字節

    長度小於或等於16383字節(14 位)的字符串。

  • |10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 字節

    長度大於16383(14位)的字符串,後4個字節表明長度。

  • |11000000| - 3 字節

    11000000 + int16(2字節)。

  • |11010000| - 5 字節

    11010000 + int32(4字節)。

  • |11100000| - 9 bytes

    11010000 + int64(8字節)。

  • |11110000| - 4 bytes

    11110000 + 24位有符號整數(3字節)。

  • |11111110| - 2 bytes

    11110000 + int8(1字節)。

  • |1111xxxx|

    極小整數,xxxx 的範圍只能是 (0001~1101),也就是1~13,可是由於0000、11十、1111都被佔用了。讀取到的 value 須要將 xxxx 減 1,也就是整數 0~12 就是最終的 value。

  • |11111111|

    表示 ziplist 的結束,也就是 zlend 的值 0xFF。

若是你以爲看的混亂了,別慌,上面的不須要所有記住,下面我會用一個鮮活的栗子(官方例子)來總結下。如下是一個包含了字符串 「2」 和 「5」 的壓縮列表:

[0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
      |             |          |       |       |     |
   zlbytes        zltail    zllen     "2"     "5"   end
複製代碼

最前面的4個字節的表明着數字 0x0f = 15(zlbytes = 15),表示這個 ziplist 總共佔用了15個字節。緊跟着的4個字節表明數字 0x0c = 12(zltail = 12),說明最後一個元素的偏移量是12,也就是 「5」 這個元素到 ziplist 開頭的長度。接着是 zllen = 2,表明總共有2個元素。以後就是實際儲存 「2」 和 「5」 的 entry。解讀下 「2」 爲何是 00 f3,00表示前一個元素長度爲0,由於它是第一個元素,f3 是 0x11110011,也就是咱們的 1111xxxx encoding類型,3 - 1 = 2正好是咱們的 「2」,「5」 也同理。 最後是 ff 結尾表示結束。

有沒有童鞋注意到官方例子總咱們儲存的是字符串的 「2」 和 「5」,可是 redis 把它當成了整數來儲存 ?這一點實際上是 redis 故意作的,不少地方都會作相似處理,目的麼,仍是爲了減小內存消耗。

最後咱們再看一個儲存字符串的例子,咱們把上面的 「5」 換成 「Hello World」,那麼原先的 「5」 這個 entry 會變成:

[02] [0b] [48 65 6c 6c 6f 20 57 6f 72 6c 64]
複製代碼

至於爲何的話,大家就對照的上面的本身試一遍,就當作練習題了。

相關文章
相關標籤/搜索