Redis 選擇hash還是string 存儲數據?

在stackoverflow 看到一個問題,Redis strings vs Redis hashes to represent JSON: efficiency?內容如下:

I want to store a JSON payload into redis. There's really 2 ways I can do this:

  1. One using a simple string keys and values.

    key:user, value:payload (the entire JSON blob which can be 100-200 KB)

    SET user:1 payload

  2. Using hashes

    HSET user:1 username "someone"
    HSET user:1 location "NY"
    HSET user:1 bio "STRING WITH OVER 100 lines"

Keep in mind that if I use a hash, the value length isn't predictable. They're not all short such as the bio example above.
Which is more memory efficient? Using string keys and values, or using a hash?

string 和 hash 直觀測試

首先我們先測試用數據測試一下,測試數據結構如下:

values = {
    "name": "gs",
    "age": 1
}

使用for 生成10w個key,key的生成規則爲:

for i in range(100000):
    key = "object:%d" % i

把數據分別以hash 和 string(values 使用 json encode 爲string )的形式存入redis。

結果如下:

hash 佔用 10.16M

hash 佔用 10.15M

這看起來和我們印象中hash 佔空間比較大的觀念不太一致,這是爲什麼呢?

這裏是因爲Redis 的hash 對象有兩種編碼方式:

  1. ziplist(2.6之前是zipmap)
  2. hashtable

當哈希對象可以同時滿足以下兩個條件時, 哈希對象使用 ziplist 編碼:

  1. 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小於 64 字節;
  2. 哈希對象保存的鍵值對數量小於 512 個;

不能滿足這兩個條件的哈希對象需要使用 hashtable 編碼。上述測試數據滿足這兩個條件,所以這裏使用的是ziplist來存儲的數據,而不是hashtable。

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

hash-max-ziplist-entries for Redis >= 2.6
hash-max-ziplist-value for Redis >= 2.6

ziplist

ziplist 編碼的數據底層是使用壓縮列表作爲底層數據結構,結構如下:

ziplist

ziplist 底層實現

hash 對象使用ziplist 保存時,程序會將保存了鍵的ziplist節點推入到列表的表尾,然後再將保存了值的ziplist節點推入列表的表尾。

使用這種方式保存時,並不需要申請多餘的內存空間,而且每個Key都要存儲一些關聯的系統信息(如過期時間、LRU等),因此和String類型的Key/Value相比,Hash類型極大的減少了Key的數量(大部分的Key都以Hash字段的形式表示並存儲了),從而進一步優化了存儲空間的使用效率。

在這篇redis memory optimization官方文章中,作者強烈推薦使用hash存儲數據

Use hashes when possible

Small hashes are encoded in a very small space, so you should try representing your data using hashes every time it is possible. For instance if you have objects representing users in a web application, instead of using different keys for name, surname, email, password, use a single hash with all the required fields.

But many times hashes contain just a few fields. When hashes are small we can instead just encode them in an O(N) data structure, like a linear array with length-prefixed key value pairs. Since we do this only when N is small, the amortized time for HGET and HSET commands is still O(1): the hash will be converted into a real hash table as soon as the number of elements it contains will grow too much (you can configure the limit in redis.conf).

This does not work well just from the point of view of time complexity, but also from the point of view of constant times, since a linear array of key value pairs happens to play very well with the CPU cache (it has a better cache locality than a hash table).

hashtable

hashtable 編碼的哈希對象使用字典作爲底層實現, 哈希對象中的每個鍵值對都使用一個字典鍵值對來保存:

  • 字典的每個鍵都是一個字符串對象, 對象中保存了鍵值對的鍵;
  • 字典的每個值都是一個字符串對象, 對象中保存了鍵值對的值。

hashtable 編碼的對象如下所示:

第二次測試

values = {
    "name": "gs",
    "age": 1,
    "intro": "long..long..long..string"
}

第二次測試方式和第一次一樣,只是把測試數據中加了一個大的字符串,以保證hash 使用hashtable 的方式存儲數據

結果如下:

hashtable: 1.13G

string: 1.13G

基本一樣,這裏應該主要是Hash類型極大的減少了Key的數量(大部分的Key都以Hash字段的形式表示並存儲了),從而進一步優化了存儲空間的使用效率。

NOTE: 讀取和寫入的速度基本一致,差別不大

回到這個問題,對於string 和 hash 該如何選擇呢?

我比較贊同下面這個答案:

具體使用哪種數據結構,其實是需要看你要存儲的數據以及使用場景。

如果存儲的都是比較結構化的數據,比如用戶數據緩存,或者經常需要操作數據的一個或者幾個,特別是如果一個數據中如果filed比較多,但是每次只需要使用其中的一個或者少數的幾個,使用hash是一個好的選擇,因爲它提供了hget 和 hmget,而無需取出所有數據再在代碼中處理。

反之,如果數據差異較大,操作時常常需要把所有數據都讀取出來再處理,使用string 是一個好的選擇。

當然,也可以聽Redis 的,放心的使用hash 吧。

還有一種場景:如果一個hash中有大量的field(成千上萬個),需要考慮是不是使用string來分開存儲是不是更好的選擇。

References

[1] Redis strings vs Redis hashes to represent JSON: efficiency?: https://stackoverflow.com/que...
[2] redis memory optimization: https://redis.io/topics/memor...
[3] Redis 設計與實現: http://redisbook.com/preview/...

參考鏈接


最後,感謝女朋友支持和包容,比❤️

也可以在公號輸入以下關鍵字獲取歷史文章:公號&小程序 | 設計模式 | 併發&協程

掃碼關注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章