redis hash是value內部為乙個hashmap,如果該map的成員數比較少,則會採用類似一維線性的緊湊格式來儲存該map,即省去了大量指標的記憶體開銷,這個引數控制對應在redis.conf配置檔案中下面2項:
以上2個條件任意乙個條件超過設定值都會轉換成真正的hashmap,也就不會再節省記憶體了,那麼這個值是不是設定的越大越好呢?答案當然是否定的,hashmap的優勢就是查詢和操作的時間複雜度都是o(1)的,而放棄hash採用一維儲存則是o(n)的時間複雜度,如果成員數量很少,則影響不大,否則會嚴重影響效能,所以要權衡好這個值的設定,總體上還是最根本的時間成本和空間成本上的權衡。
# hash資料結構
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
# list資料結構
list-max-ziplist-entries 512
list-max-ziplist-value 64
# set資料結構
set-max-intset-entries 512
# zset資料結構
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
對於redis的hashmap和zset來說,如果內部元素很少,依舊使用二維結構會很浪費空間。目前有個叫做ziplist的資料結構,該結構是乙個緊湊的位元組陣列結構,每個元素都是緊挨著的。
set集合的元素很少的時候,且都是整數
的時候,會採用intset這個資料結構。這是乙個緊湊的整形陣列結構。如果加入了字串就會轉換為hashmap結構。
配置引數:set-max-intset-entries
以hash資料結構的ziplist(redis 2.6之前是zipmap)為例優化方案是使用 hash 結構,由於 hash 結構會在單個 hash 元素在不足一定數量時進行壓縮儲存,所以可以大量節約記憶體。這一點 string 結構裡是不存在的。
省記憶體的原因是新建乙個 hash 物件時開始是用 ziplist(又稱為 small hash)來儲存的。這個 ziplist 其實並不是 hash table,但是 ziplist 相比正常的 hash 實現可以節省不少 hash 本身需要的一些元資料儲存開銷。 儘管 ziplist 的新增,刪除,查詢都是 o(n),但是由於一般物件的 field 數量都不太多。 所以使用 ziplist 也是很快的。
hash時什麼情況才用ziplist?同時滿足以下條件:
雜湊物件儲存的鍵值對數量小於512個;配置引數:hash-max-ziplist-entries。
雜湊物件儲存的所有鍵值的字串長度小於64位元組;配置引數:hash-max-ziplist-value。
使用ziplist的優點:
為什麼不直接用hastable
: 相比hashtable,ziplist結構少了指標,大大的減少了記憶體的使用,而記憶體對於redis來說彌足珍貴;
為什麼不用 linklist
: ziplist儲存時記憶體分配是連續的,查詢更快,這裡的快只是相對雙端佇列;
ziplist如何實現hash儲存?
將同一鍵值對的兩個節點緊挨著儲存,儲存鍵的節點在前,儲存值的節點在後,新加入的鍵值對,放在壓縮列表表尾。
舉個例子, 如果我們執行以下 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
編碼, 那麼這個值物件將會是圖 8-9 所示的樣子, 其中物件所使用的壓縮列表如圖 8-10 所示。
hash 物件使用ziplist 儲存時,程式會將儲存了鍵的ziplist節點推入到列表的表尾,然後再將儲存了值的ziplist節點推入列表的表尾。
使用這種方式儲存時,並不需要申請多餘的記憶體空間,而且每個key都要儲存一些關聯的系統資訊(如過期時間、lru等),因此和string型別的key/value相比,hash型別極大的減少了key的數量(大部分的key都以hash欄位的形式表示並儲存了)
,從而進一步優化了儲存空間的使用效率。
在這篇redis memory-optimization#use-hashes-when-possible官方文章中,作者強烈推薦使用hash儲存資料。
# 雜湊物件只包含乙個鍵和值都不超過 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"
除了鍵的長度太大會引起編碼轉換之外, 值的長度太大也會引起編碼轉換
# 雜湊物件只包含乙個鍵和值都不超過 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"
最後, 以下**展示了雜湊物件因為包含的鍵值對數量過多而引起編碼轉換的情況:
# 建立乙個包含 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"
okredis> hlen numbers
(integer) 513
# 編碼改變
redis> object encoding numbers
"hashtable"
原儲存方式: 採用string資料結構儲存
set bucket:1111123 999
get bucket:1111123
> 999
將資料分段(取1111123的前四位作為hash資料結構的key),每一段使用乙個 hash 結構儲存,保證了每個 hash 內部只包含 3 位的 key,也就是 1000 個。如:
hset bucket:1111 123 999
hget bucket:1111 123
> 999
這樣公共的字首只存了一次,也節約了一部分記憶體。總的 2 個優化:
hash 的壓縮列表
公共部分只存一次(公共字首bucket:1111在string資料結構中存了1000次,而hash資料結構中只存了一次)
Redis儲存優化 小物件壓縮
小物件壓縮 32bit vs 64bit 小物件壓縮 public class arraymap keys.add k values.add v return null public v get k k return null public v delete k k return null 新doc...
Redis 小物件壓縮
redis 是乙個非常耗費記憶體的資料庫,它所有的資料都放在記憶體裡。如果我們不注意節約使用記憶體,redis 就會因為我們的無節制使用出現記憶體不足而崩潰。1 ziplist 是乙個緊湊的位元組陣列結構,每個元素直接都是緊挨著,資料結構如下圖 1 object encoding key 查詢key...
小物件壓縮
鑑於redis是純記憶體資料庫,為了盡可能的節省記憶體開銷避免因為記憶體不足而崩潰,redis對一部分資料結構進行了優化。編譯 如果使用32位進行編譯,內部所有資料結構所使用的指標空間占用會少一半,如果使用記憶體不超過4g,可以考慮使用32位進行編譯,如果不足還可以通過增加例項的方式來解決。壓縮 這...