ziplist是一個經過特殊編碼的雙向鍊表,它的設計目標就是為了提高存儲效率,本文對其進行一定的分析。
什麼是ziplist
Redis官方對於ziplist的定義是(出自ziplist.c的文件頭部注釋):
The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and integer values, where integers are encoded as actual integers instead of a series of characters. It allows push and pop operations on either side of the list in O(1) time.
即,ziplist是一個經過特殊編碼的雙向鍊表,它的設計目標就是為了提高存儲效率。ziplist可以用於存儲字符串或整數,其中整數是按真正的二進位表示進行編碼的,而不是編碼成字符串序列。它能以O (1)的時間複雜度在表的兩端提供push和pop操作。
實際上,ziplist充分體現了Redis對於存儲效率的追求。一個普通的雙向鍊表,鍊表中每一項都占用獨立的一塊內存,各項之間用地址指針(或引用)連接起來。這種方式會帶來大量的內存碎片,而且地址指針也會占用額外的內存。而ziplist卻是將表中每一項存放在前後連續的地址空間內,一個ziplist整體占用一大塊內存。它是一個表(list),但其實不是一個鍊表(linked list)。
ziplist的數據結構定義
上面的定義中還值得注意的一點是: , , 既然占據多個位元組,那麼在存儲的時候就有大端(big endian)和小端(little endian)的區別。ziplist採取的是小端模式來存儲。在解析多個位元組序列時需要特別注意調換順序。
為什麼最後一個值固定為255呢?
這是因為:255已經定義為ziplist結束標記 的值了。在ziplist的很多操作的實現中,都會根據數據項的第1個位元組是不是255來判斷當前是不是到達ziplist的結尾了,因此一個正常的數據的第1個位元組(也就是 的第1個位元組)是不能夠取255這個值的,否則就衝突了。
ziplist entry
pre_entry_length
根據編碼方式的不同, pre_entry_length 域可能占用 1 位元組或者 5 位元組:
encoding 和 length
encoding 和 length 兩部分一起決定了 content 部分所保存的數據的類型(以及長度)。
其中, encoding 域的長度為兩個 bit , 它的值可以是 00 、 01 、 10 和 11 :
content
content 部分保存著節點的內容,類型和長度由 encoding 和 length 決定。 先分析前兩個bit的值,對應到具體的數據類型,然後再根據上表分析後面的內容的真實長度。
例子
這個例子來自 zhangtielei :
上圖是一份真實的ziplist數據。我們逐項解讀一下:
總結一下,這個ziplist里存了4個數據項,分別為:
注意:這個ziplist是通過兩個 hset 命令創建出來的:
hset myinfo name tielei
hset myinfo age 20
hash與ziplist
hash是Redis中可以用來存儲一個對象結構的比較理想的數據類型。一個對象的各個屬性,正好對應一個hash結構的各個field。
我們在網上很容易找到這樣一些技術文章,它們會說存儲一個對象,使用hash比string要節省內存。實際上這麼說是有前提的,具體取決於對象怎麼來存儲。如果你把對象的多個屬性存儲到多個key上(各個屬性值存成string),當然占的內存要多。但如果你採用一些序列化方法,比如Protocol Buffers,或者Apache Thrift,先把對象序列化為位元組數組,然後再存入到Redis的string中,那麼跟hash相比,哪一種更省內存,就不一定了。
當然,hash比序列化後再存入string的方式,在支持的操作命令上,還是有優勢的:它既支持多個field同時存取(hmset/hmget),也支持按照某個特定的field單獨存取(hset/hget)。
實際上,hash隨著數據的增大,其底層數據結構的實現是會發生變化的,當然存儲效率也就不同。在field比較少,各個value值也比較小的時候,hash採用ziplist來實現;而隨著field增多和value值增大,hash可能會變成dict來實現。當hash底層變成dict來實現的時候,它的存儲效率就沒法跟那些序列化方式相比了。
當我們為某個key第一次執行 hset key field value 命令的時候,Redis會創建一個hash結構,這個新創建的hash底層就是一個ziplist。
當隨著數據的插入,hash底層的這個ziplist就可能會轉成dict。那麼到底插入多少才會轉呢?
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
這個配置的意思是說,在如下兩個條件之一滿足的時候,ziplist會轉成dict:
Redis的hash之所以這樣設計,是因為當ziplist變得很大的時候,它有如下幾個缺點:
API和時間複雜度