Redis存儲結(jié)構(gòu)體信息,選hash還是string?
在講到使用hash還是string存儲的選擇前,先了解Redis的hash和string結(jié)構(gòu)。
以下資料引自老錢的Redis深度歷險
string
string和hash都是Redis的一種數(shù)據(jù)結(jié)構(gòu)。string結(jié)構(gòu)常用來緩存用戶信息,通常將用戶信息結(jié)構(gòu)體使用JSON序列化成字符串,然后將序列化后的字符串存入Redis進行緩存。
String數(shù)據(jù)結(jié)構(gòu)Redis的字符串是動態(tài)字符串,可以修改,內(nèi)部結(jié)構(gòu)類似于Java的ArrayList,采用預(yù)分配冗余空間的方式來減少內(nèi)存的頻繁分配。如上圖鎖實,內(nèi)部為當(dāng)前字符串實際分配的空間capacity,一般高于實際字符串長度len。使用的指令有set, get, mset, mget等
hash
Redis的hash相當(dāng)于Java的HashMap,內(nèi)部結(jié)構(gòu)實現(xiàn)與HashMap一致,即數(shù)組+鏈表結(jié)構(gòu)
hash數(shù)據(jù)結(jié)構(gòu)不過Redis的hash的值只能是字符串,rehash方式不一樣,為了提高性能,Redis保留新舊兩個hash結(jié)構(gòu),采用漸進式rehash策略,查詢時會同事查詢兩個hash結(jié)構(gòu),在后續(xù)的定時任務(wù)中以及hash操作指令中,循序漸進將舊hash的內(nèi)容遷移到xinhash中,直至完全取代舊hash。hash移除最后一個元素后會自動被刪除,內(nèi)存被回收。
前面說到string適合存儲用戶信息,而hash結(jié)構(gòu)也可以存儲用戶信息,不過是對每個字段單獨存儲,因此可以在查詢時獲取部分字段的信息,節(jié)省網(wǎng)絡(luò)流量。
因此就引出了這篇文章,存儲結(jié)構(gòu)體信息是用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:
- 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
- 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存儲更有效率
這個問題底下有個開發(fā)者回答的非常好,這里翻譯出來供大家一起學(xué)習(xí)討論,如果有更好的方案,歡迎提出來 首先,答者建議參考redis官方的內(nèi)存優(yōu)化的文章:https://redis.io/topics/memory-optimization,用來理解官方的開發(fā)者是內(nèi)存優(yōu)化方面基于什么考慮。
之后,答者列出了四個方案并給出了各個方案的利弊
1. 存儲整個對象,其中JSON序列化過的字符串作為key
INCR?id:users
SET?user:{id}?'{"name":"Fred","age":25}'
SADD?users?{id}
- 優(yōu)勢:可以認為是“最佳實踐”,因為每個對象都是全特性的key,JSON解析特別塊,尤其是一次性查詢很多個字段的時候
- 劣勢:如果只查詢一個字段,速度就顯得比較慢了
2. 在hash中存儲每個對象的屬性
INCR?id:users
HMSET?user:{id}?name?"Fred"?age?25
SADD?users?{id}
- 優(yōu)勢:這也可以認為是最佳時間。每個對象都是一個全特性的key。不需要解析JSON字符串
- 劣勢:如果要查詢對象的全部字段會比較慢。嵌套類型的對象(即對象里面還包著對象)無法輕易存儲
3. 將對象轉(zhuǎn)化為JSON字符串,用hash結(jié)構(gòu)存儲
INCR?id:users
HMSET?users?{id}?'{"name":"Fred","age":25}'
這個方案可以僅用兩個key,不需要很多key。但是沒法對每個用戶對象設(shè)置TTL(Time to Live,剩余生存時間),因為對象僅僅是hash中的一個字段,而不是全特性的key
- 優(yōu)勢:JSON解析很快,尤其是一次查詢多個字段時,對主key的命名空間污染更少
- 劣勢:如果要存儲很多對象,那么內(nèi)存使用和方案1相當(dāng)。當(dāng)只需要查詢一個字段時,會比方案2速度慢。答者不認為這是一個“最佳實踐”
4. 存儲對象的每個屬性作為單獨的key
INCR?id:users
SET?user:{id}:name?"Fred"
SET?user:{id}:age?25
SADD?users?{id}
根據(jù)上面的文章,即redis內(nèi)存優(yōu)化,這個方案不推薦(除非對象的屬性需要專門設(shè)置TTL或者別的設(shè)置)
- 優(yōu)勢:對象的屬性是全特征key,對于應(yīng)用來說比較好處理
- 劣勢:慢,內(nèi)存消耗更大,不是一個“最佳實踐”。對主key的命名空間有很大污染
總的來說,方案4是最不推薦的,方案1和方案2非常相似,也很常見。答者更推薦方案1,因為這個方案允許存儲更復(fù)雜的對象(也就是說對象可以有很多層嵌套)。方案3通常用在對命名空間比較有要求的場景下,比如說不想要太多key,不關(guān)心TTL等參數(shù)
