《閑扯Redis六》Redis五種數據類型之Hash型



一、前言

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 編碼方式之間存在轉換

大道七哥,有趣話不多


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM