《閒扯Redis六》Redis五種資料型別之Hash型

jstarseven發表於2020-07-23

一、前言

Redis 提供了5種資料型別:String(字串)、Hash(雜湊)、List(列表)、Set(集合)、Zset(有序集合),理解每種資料型別的特點對於redis的開發和運維非常重要。

原文解析

Redis五種資料型別

Redis 中的 hash 是我們經常使用到的一種資料型別,根據使用方式的不同,可以應用到很多場景中。

二、實現分析

 由上述結構圖可知,Hash型別有以下兩種實現方式:

1、ziplist 編碼的雜湊物件使用壓縮列表作為底層實現
2、hashtable 編碼的雜湊物件使用字典作為底層實現

1.ziplist 編碼作為底層實現

ziplist 編碼的雜湊物件使用壓縮列表作為底層實現, 每當有新的鍵值對要加入到雜湊物件時, 程式會先將儲存了鍵的壓縮列表節點推入到壓縮列表表尾, 然後再將儲存了值的壓縮列表節點推入到壓縮列表表尾, 因此:

儲存了同一鍵值對的兩個節點總是緊挨在一起, 儲存鍵的節點在前, 儲存值的節點在後;
先新增到雜湊物件中的鍵值對會被放在壓縮列表的表頭方向,而後來新增到雜湊物件中的鍵值對會被放在壓縮列表的表尾方向。

例如, 我們執行以下 HSET 命令, 那麼伺服器將建立一個列表物件作為 profile 鍵的值:

redis> HSET profile name "Tom"
(integer) 1

redis> HSET profile age 25
(integer) 1

redis> HSET profile career "Programmer"
(integer) 1

profile 鍵的值物件使用的是 ziplist 編碼, 其中物件所使用的壓縮列表結構如下圖所示。

Redis五種資料型別
Redis五種資料型別

2.hashtable 編碼作為底層實現

hashtable 編碼的雜湊物件使用字典作為底層實現, 雜湊物件中的每個鍵值對都使用一個字典鍵值對來儲存:

字典的每個鍵都是一個字串物件, 物件中儲存了鍵值對的鍵;

字典的每個值都是一個字串物件, 物件中儲存了鍵值對的值。

例如, 如果前面 profile 鍵建立的不是 ziplist 編碼的雜湊物件, 而是 hashtable 編碼的雜湊物件, 那麼這個雜湊物件結構如下圖所示。

Redis五種資料型別

三、命令實現

因為雜湊鍵的值為雜湊物件, 所以用於雜湊鍵的所有命令都是針對雜湊物件來構建的, 下表列出了其中一部分雜湊鍵命令, 以及這些命令在不同編碼的雜湊物件下的實現方法。

命令 ziplist 編碼實現方法 hashtable 編碼的實現方法
HSET 首先呼叫 ziplistPush 函式, 將鍵推入到壓縮列表的表尾, 然後再次呼叫 ziplistPush 函式, 將值推入到壓縮列表的表尾。 呼叫 dictAdd 函式, 將新節點新增到字典裡面。
HGET 首先呼叫 ziplistFind 函式, 在壓縮列表中查詢指定鍵所對應的節點, 然後呼叫 ziplistNext 函式, 將指標移動到鍵節點旁邊的值節點, 最後返回值節點。 呼叫 dictFind 函式, 在字典中查詢給定鍵, 然後呼叫dictGetVal 函式, 返回該鍵所對應的值。
HEXISTS 呼叫 ziplistFind 函式, 在壓縮列表中查詢指定鍵所對應的節點, 如果找到的話說明鍵值對存在, 沒找到的話就說明鍵值對不存在。 呼叫 dictFind 函式, 在字典中查詢給定鍵, 如果找到的話說明鍵值對存在, 沒找到的話就說明鍵值對不存在。
HDEL 呼叫 ziplistFind 函式, 在壓縮列表中查詢指定鍵所對應的節點, 然後將相應的鍵節點、 以及鍵節點旁邊的值節點都刪除掉。 呼叫 dictDelete 函式, 將指定鍵所對應的鍵值對從字典中刪除掉。
HLEN 呼叫 ziplistLen 函式, 取得壓縮列表包含節點的總數量, 將這個數量除以 2 , 得出的結果就是壓縮列表儲存的鍵值對的數量。 呼叫 dictSize 函式, 返回字典包含的鍵值對數量, 這個數量就是雜湊物件包含的鍵值對數量。
HGETALL 遍歷整個壓縮列表, 用 ziplistGet 函式返回所有鍵和值(都是節點)。 遍歷整個字典, 用 dictGetKey 函式返回字典的鍵, 用dictGetVal 函式返回字典的值。

四、編碼轉換

當雜湊物件可以同時滿足以下兩個條件時, 雜湊物件使用 ziplist 編碼:

雜湊物件儲存的所有鍵值對的鍵和值的字串長度都小於 64 位元組;

雜湊物件儲存的鍵值對數量小於 512 個;

不能滿足這兩個條件的雜湊物件需要使用 hashtable 編碼。

注意:這兩個條件的上限值是可以修改的, 具體請看配置檔案中關於 hash-max-ziplist-value 選項和 hash-max-ziplist-entries 選項的說明。

對於使用 ziplist 編碼的列表物件來說, 當使用 ziplist 編碼所需的兩個條件的任意一個不能被滿足時, 物件的編碼轉換操作就會被執行: 原本儲存在壓縮列表裡的所有鍵值對都會被轉移並儲存到字典裡面, 物件的編碼也會從 ziplist 變為 hashtable 。

以下程式碼展示了雜湊物件編碼轉換的情況:

1.鍵的長度太大引起編碼轉換

# 雜湊物件只包含一個鍵和值都不超過 64 個位元組的鍵值對
redis> HSET book name "Mastering C++ in 21 days"
(integer) 1

redis> OBJECT ENCODING book
"ziplist"

# 向雜湊物件新增一個新的鍵值對,鍵的長度為 66 位元組
redis> HSET book long_long_long_long_long_long_long_long_long_long_long_description "content"
(integer) 1

# 編碼已改變
redis> OBJECT ENCODING book
"hashtable"

2.值的長度太大引起編碼轉換

# 雜湊物件只包含一個鍵和值都不超過 64 個位元組的鍵值對
redis> HSET blah greeting "hello world"
(integer) 1

redis> OBJECT ENCODING blah
"ziplist"

# 向雜湊物件新增一個新的鍵值對,值的長度為 68 位元組
redis> HSET blah story "many string ... many string ... many string ... many string ... many"
(integer) 1

# 編碼已改變
redis> OBJECT ENCODING blah
"hashtable"

3.鍵值對數量過多引起編碼轉換

# 建立一個包含 512 個鍵值對的雜湊物件
redis> EVAL "for i=1, 512 do redis.call('HSET', KEYS[1], i, i) end" 1 "numbers"
(nil)

redis> HLEN numbers
(integer) 512

redis> OBJECT ENCODING numbers
"ziplist"

# 再向雜湊物件新增一個新的鍵值對,使得鍵值對的數量變成 513 個
redis> HMSET numbers "key" "value"
OK

redis> HLEN numbers
(integer) 513

# 編碼改變
redis> OBJECT ENCODING numbers
"hashtable"

五、要點總結

1.Hash型別兩種編碼方式,ziplist 與 hashtable

2.hashtable 編碼的雜湊物件使用字典作為底層實現

3.ziplist 與 hashtable 編碼方式之間存在轉換

大道七哥,有趣話不多

相關文章