生活随笔
收集整理的這篇文章主要介紹了
美团在Redis上踩过的一些坑-4.redis内存使用优化
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
轉載請注明出處哈:http://carlosfu.iteye.com/blog/2254154
?? ?
?一、背景: 選擇合適的使用場景
? ?很多時候Redis被誤解并亂用了,造成的Redis印象:耗內存、價格成本很高:
? ?1.?為了“趕時髦”或者對于Mysql的“誤解”在一個并發量很低的系統使用Redis,將原來放在Mysql數據全部放在Redis中。
? ? ?----(Redis比較適用于高并發系統,如果是一些復雜Mis系統,用Redis反而麻煩,因為單從功能講Mysql要更為強大,而且Mysql的性能其實已經足夠了。)
? ?2. 覺得Redis就是個KV緩存
? ? ?-----(Redis支持多數據結構,并且具有很多其他豐富的功能)
? ?3. 喜歡做各種對比,比如Mysql, Hbase, Redis等等
? ? -----(每種數據庫都有自己的使用場景,比如Hbase吧,我們系統的個性化數據有1T,此時放在Redis根本就不合適,而是將一些熱點數據放在Redis)
? ? 總之就是在合適的場景,選擇合適的數據庫產品。
? 附贈兩個名言:
Evan Weaver, Twitter, March 2009 寫道 Everything runs from memory in Web 2.0!
Tim Gray 寫道 Tape is Dead, Disk is Tape, Flash is Disk, RAM Locality is king. (磁帶已死,磁盤是新磁帶,閃存是新磁盤,隨機存儲器局部性是為王道) ??
二、一次string轉化為hash的優化
1. 場景:
? ? 用戶id: userId,
? ? 用戶微博數量:weiboCount? ??
| userId(用戶id) | weiboCount(微博數) |
| 1 | 2000 |
| 2 | 10 |
| 3 | 288 |
| .... | ... |
| 1000000 | 1000 |
?
2. 實現方法: (1) 使用Redis字符串數據結構, userId為key, weiboCount作為Value (2) 使用Redis哈希結構,hashkey只有一個, key="allUserWeiboCount",field=userId,fieldValue= weiboCount (3) 使用Redis哈希結構, ?hashkey為多個, key=userId/100, field=userId%100, fieldValue= weiboCount 前兩種比較容易理解,第三種方案解釋一下:每個hashKey存放100個hash-kv,field=userId%100,也就是
| userId | hashKey | field |
| 1 | 0 | 1 |
| 2 | 0 | 2 |
| 3 | 0 | 3 |
| ... | .... | ... |
| 99 | 0 | 99 |
| 100 | 1 | 0 |
| 101 | 1 | 1 |
| .... | ... | ... |
| 9999 | 99 | 99 |
| 100000 | 1000 | 0 |
?
注意:
為了排除共享對象的問題,在真實測試時候所有key,field,value都用字符串類型。
?
3. 獲取方法:
?
Java代碼??
#獲取userId=5003用戶的微博數?? (1)?get?u:5003?? (2)?hget?allUser?u:5003?? (3)?hget?u:50?f:3?? ?
?
4. 內存占用量對比(100萬用戶 userId u:1~u:1000000)?
??
Java代碼??
#方法一?Memory?? used_memory:118002640?? used_memory_human:112.54M?? used_memory_rss:127504384?? used_memory_peak:118002640?? used_memory_peak_human:112.54M?? used_memory_lua:36864?? mem_fragmentation_ratio:1.08?? mem_allocator:jemalloc-3.6.0?? ---------------------------------------------------?? #方法二?Memory?? used_memory:134002968?? used_memory_human:127.80M?? used_memory_rss:144261120?? used_memory_peak:134002968?? used_memory_peak_human:127.80M?? used_memory_lua:36864?? mem_fragmentation_ratio:1.08?? mem_allocator:jemalloc-3.6.0?? --------------------------------------------------------?? #方法三?Memory?? used_memory:19249088?? used_memory_human:18.36M?? used_memory_rss:26558464?? used_memory_peak:134002968?? used_memory_peak_human:127.80M?? used_memory_lua:36864?? mem_fragmentation_ratio:1.38?? mem_allocator:jemalloc-3.6.0?? ??
?那么為什么第三種能少那么多內存呢?之前有人說用了共享對象的原因,現在我將key,field,value全部都變成了字符串,仍然還是節約很多內存。
?之前我也懷疑過是hashkey,field的字節數少造成的,但是我們下面通過一個實驗看就清楚是為什么了。當我將hash-max-ziplist-entries設置為2并且重啟后,所有的hashkey都變為了hashtable編碼。
?同時我們看到了內存從18.36M變為了122.30M,變化還是很大的。
?
Java代碼??
127.0.0.1:8000>?object?encoding?u:8417?? "ziplist"?? 127.0.0.1:8000>?config?set?hash-max-ziplist-entries?2?? OK?? 127.0.0.1:8000>?debug?reload?? OK?? (1.08s)?? 127.0.0.1:8000>?config?get?hash-max-ziplist-entries?? 1)?"hash-max-ziplist-entries"?? 2)?"2"?? 127.0.0.1:8000>?info?memory?? #?Memory?? used_memory:128241008?? used_memory_human:122.30M?? used_memory_rss:137662464?? used_memory_peak:134002968?? used_memory_peak_human:127.80M?? used_memory_lua:36864?? mem_fragmentation_ratio:1.07?? mem_allocator:jemalloc-3.6.0?? 127.0.0.1:8000>?object?encoding?u:8417?? "hashtable"?? ? ?
?
?
?
? 內存使用量:
??
??
5. 導入數據代碼(不考慮代碼優雅性,單純為了測試,勿噴) 注意: 為了排除共享對象的問題,這里所有key,field,value都用字符串類型。 ? Java代碼??
package?com.carlosfu.redis;?? ?? import?java.util.ArrayList;?? import?java.util.HashMap;?? import?java.util.List;?? import?java.util.Map;?? import?java.util.Random;?? ?? import?org.junit.Test;?? ?? import?redis.clients.jedis.Jedis;?? ?? ? ? ? ? ? ? ?? public?class?TestRedisMemoryOptimize?{?? ?? ????private?final?static?int?TOTAL_USER_COUNT?=?1000000;?? ?? ????private?final?static?String?HOST?=?"127.0.0.1";?? ?? ????private?final?static?int?PORT?=?6379;?? ?? ????? ? ?? ????@Test?? ????public?void?testString()?{?? ????????int?mBatchSize?=?2000;?? ????????Jedis?jedis?=?null;?? ????????try?{?? ????????????jedis?=?new?Jedis(HOST,?PORT);?? ????????????List<String>?kvsList?=?new?ArrayList<String>(mBatchSize);?? ????????????for?(int?i?=?1;?i?<=?TOTAL_USER_COUNT;?i++)?{?? ????????????????String?key?=?"u:"?+?i;?? ????????????????kvsList.add(key);?? ????????????????String?value?=?"v:"?+?i;?? ????????????????kvsList.add(value);?? ????????????????if?(i?%?mBatchSize?==?0)?{?? ????????????????????System.out.println(i);?? ????????????????????jedis.mset(kvsList.toArray(new?String[kvsList.size()]));?? ????????????????????kvsList?=?new?ArrayList<String>(mBatchSize);?? ????????????????}?? ????????????}?? ????????}?catch?(Exception?e)?{?? ????????????e.printStackTrace();?? ????????}?finally?{?? ????????????if?(jedis?!=?null)?{?? ????????????????jedis.close();?? ????????????}?? ????????}?? ????}?? ?? ????? ? ?? ????@Test?? ????public?void?testHash()?{?? ????????int?mBatchSize?=?2000;?? ????????String?hashKey?=?"allUser";?? ????????Jedis?jedis?=?null;?? ????????try?{?? ????????????jedis?=?new?Jedis(HOST,?PORT);?? ????????????Map<String,?String>?kvMap?=?new?HashMap<String,?String>();?? ????????????for?(int?i?=?1;?i?<=?TOTAL_USER_COUNT;?i++)?{?? ????????????????String?key?=?"u:"?+?i;?? ????????????????String?value?=?"v:"?+?i;?? ????????????????kvMap.put(key,?value);?? ????????????????if?(i?%?mBatchSize?==?0)?{?? ????????????????????System.out.println(i);?? ????????????????????jedis.hmset(hashKey,?kvMap);?? ????????????????????kvMap?=?new?HashMap<String,?String>();?? ????????????????}?? ????????????}?? ????????}?catch?(Exception?e)?{?? ????????????e.printStackTrace();?? ????????}?finally?{?? ????????????if?(jedis?!=?null)?{?? ????????????????jedis.close();?? ????????????}?? ????????}?? ????}?? ?? ????? ? ?? ????@Test?? ????public?void?testSegmentHash()?{?? ????????int?segment?=?100;?? ????????Jedis?jedis?=?null;?? ????????try?{?? ????????????jedis?=?new?Jedis(HOST,?PORT);?? ????????????Map<String,?String>?kvMap?=?new?HashMap<String,?String>();?? ????????????for?(int?i?=?1;?i?<=?TOTAL_USER_COUNT;?i++)?{?? ????????????????String?key?=?"f:"?+?String.valueOf(i?%?segment);?? ????????????????String?value?=?"v:"?+?i;?? ????????????????kvMap.put(key,?value);?? ????????????????if?(i?%?segment?==?0)?{?? ????????????????????System.out.println(i);?? ????????????????????int?hash?=?(i?-?1)?/?segment;?? ????????????????????jedis.hmset("u:"?+?String.valueOf(hash),?kvMap);?? ????????????????????kvMap?=?new?HashMap<String,?String>();?? ????????????????}?? ????????????}?? ????????}?catch?(Exception?e)?{?? ????????????e.printStackTrace();?? ????????}?finally?{?? ????????????if?(jedis?!=?null)?{?? ????????????????jedis.close();?? ????????????}?? ????????}?? ????}?? ?? }?? ?
三、結果對比
?redis核心對象 數據類型 + 編碼方式 + ptr ?分段hash也不會造成drift
| 方案 | 優點 | 缺點 |
| string | 直觀、容易理解 | 內存占用較大key值分散、不變于計算整體 |
| hash | 直觀、容易理解、整合整體 | 內存占用大一個key占用過大內存,如果是redis-cluster會出 現data drift ? |
| segment-hash | 內存占用量小,雖然理解不夠直觀,但是總體上是最優的。 | 理解不夠直觀。 |
?
四、結論:
? ?在使用Redis時,要選擇合理的數據結構解決實際問題,那樣既可以提高效率又可以節省內存。所以此次優化方案三為最佳。
附圖一張:redis其實是一把瑞士軍刀:
?
?
?
?
總結
以上是生活随笔為你收集整理的美团在Redis上踩过的一些坑-4.redis内存使用优化的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。