一、Hash類型簡介
Redis的Hash類型是 Redis 3.2 版本引入的一個數據結構,它允許你在一個鍵下面存儲多個字段和值。在 Redis 內部,Hash 類型可以有多種底層數據結構來實現,這取決于存儲的數據量和特定的使用模式。哈希類型適用于存儲對象,例如用戶信息、商品詳情等。通過使用哈希,可以更方便地對數據進行操作和查詢。
Hash的底層數據結構在早期版本使用ZipList(壓縮列表)和HashTable,在某些版本中(尤其是較新版本),Redis 可能還會使用 Quicklist 作為 Hash 類型的底層數據結構。Quicklist 是 ziplist 和 linkedlist(鏈表)的混合體,它通過多個 ziplist 節點組成一個鏈表來提高大數據量下的性能。Quicklist 提供了更好的性能平衡,特別是在需要頻繁插入和刪除操作時。在 Redis 7.0 中,ZipList數據結構已經廢棄了,交由 ListPack 數據結構來實現了。
二、Hash使用場景
- 存儲用戶信息:如果你需要存儲用戶信息,如用戶名、郵箱、年齡等,可以使用 Redis Hash 存儲。
- 緩存對象:如果你有一個對象需要頻繁更新和訪問,可以將這個對象的屬性以 Hash 的形式存儲到 Redis 中。
- 會話存儲:可以使用 Redis Hash 存儲用戶會話信息,如用戶的登錄狀態、購物車內容等。
- 頻繁更新的統計數據,如用戶積分、訪問次數等。
- 程序中的頻繁讀取和偶爾更新的配置數據。
- 存儲地理位置信息的應用(如地圖服務),Redis Hash 可以用來存儲地點ID、經緯度、地址等信息。
三、底層編碼方式
編碼方式主要分兩部分講,一個是Redis6中的編碼方式,一個是Redis7的編碼方式。
在Redis6中,Hash數據結構的底層實現有兩種編碼方式,分別是ziplist(壓縮列表)和hashtable(哈希表),Quicklist 是 ziplist 和 linkedlist(鏈表)的混合體,它通過多個 ziplist 節點組成一個鏈表來提高大數據量下的性能。Quicklist 提供了更好的性能平衡,特別是在需要頻繁插入和刪除操作時。在 Redis 7.0 中,ZipList數據結構已經廢棄了,交由 ListPack 數據結構來實現了。
四、Hash底層數據結構
Redis的Hash類型的儲存結構,分別是ZipLIst、Quicklis、ListPack和HashTable ,下面對這些結構分別進行分析。
①ZIPLIST底層數據結構
ZipList是經過特殊編碼的雙向鏈接列表,對于上面提到的LinkedList鏈表結構,由于內存中不是連續的,LinkedList多使用指針導致浪費內存空間、內存使用率都較低。為了解決這個問題,引入了 ZipList這種數據結構。 ZipList是一種有順序、內存連續的數據結構。具備節省內存空間、提升內存使用率,適用于元素數量少且長度比較小的場景。在Redis 7.0版本之前是List、Hash、ZSet底層實現之一,但是自身也存在其他問題,因此在 Redis 7.0后被ListPack完全替換。
Ⅰ、ZIPLIST類對象
typedef struct ziplist{ /*ziplist分配的內存大小*/ uint32_t zlbytes; /*達到尾部的偏移量 */ uint32_t zltail; /*存儲元素實體個數*/ uint16_t zllen; /*存儲內容實體元素*/ unsigned char* content[]; /*尾部標識*/ unsigned char zlend;}ziplist;
- zlbytes:壓縮列表的字節長度。
- zltail:壓縮列表尾元素相對于壓縮列表起始地址的偏移量(目的是為了直接定位到尾節點,方便反向查詢)。
- zllen:壓縮列表的元素個數。
- entry:各個節點數據。
- zlend:壓縮列表的結尾,占一個字節,一直是0xFF(255)。
Ⅱ、ZIPLIST中節點(entry)類對象
typedef struct ziplistEntry {
unsigned int pre_entry_len; // 前一個entry的長度編碼大小
unsigned char encoding; // 節點編碼方式
unsigned char *content; // 指向當前entry數據的指針(節點的起始指針)
} ziplistEntry;
- pre_entry_length: 記錄了前一個節點的長度,通過這個值,可以進行指針計算,從而跳轉到上一個節點。
- 根據編碼方式的不同, pre_entry_length 域可能占用 1 字節或者 5 字節:1 字節,如果前一節點的長度小于 254 字節,便使用一個字節保存它的值。5 字節,如果前一節點的長度大于等于 254 字節,那么將第 1 個字節的值設為 254 ,然后用接下來的 4 個字節保存實際長度。
- encoding表示編碼類型
字符串類型: 字符串類型有1、2、5三種編碼長度,前兩位表示編碼類型,剩余位表示字符串長度。
00|aaaaaa:存儲長度小于等于63byte的字符串。
01|aaaaaa bbbbbbbb:存儲長度小于等于16383byte的字符串。
10|...... bbbbbbbb cccccccc dddddddd eeeeeeee:存儲長度小于等于4294967295byte的字符串,'.'固定為0。
整數類型:整數類型的編碼長度統一位1字節。
1100 0000:表示16位有符號整數,content占用2byte。
1101 0000:表示32位有符號整數,content占用4byte。
1110 0000:表示64位有符號整數,content占用8byte。
1111 0000:表示24位有符號整數,content占用3byte。
1111 1110:表示8位有符號整數,content占用1byte。
1111 0001 - 1111 1101:沒有content部分,依次表示整數0-12。
- content: 保存當前entry節點數據,可以是字符串或整數。
Ⅲ、ZIPLIST底層數據結構
Ⅳ、ZIPLIST數據結構存在問題
- 查詢效率
數據過多,導致鏈表過長,可能影響查詢性能
- 內存重分配&&連鎖更新
ZipList 在更新或者新增時候,如空間不夠則需要對整個列表進行重新分配。當新插入的元素較大時,可能會導致后續元素的prevlen 占用空間都發生變化,從而引起「連鎖更新」問題,導致每個元素的空間都