最近在大量使用Redis來進行數據統計前的清洗和整理,每天的數據量超5千萬+,在開發過程中,數據量小,着重注意業務規則的處理,在上線基本測試后發現了大量的問題,其中之一就是Redis存儲數據過多,內存的使用量大大增加。進過簡單分析,對存儲非常頻繁的實體類進行了改進,字段名字進行縮寫處理,一下子就減少了很多內存使用量。在對Redis的研究過程中,發現了以下這篇文章:Redis上踩過的一些坑-美團 ,發現其中 有一節內容:“四、redis內存使用優化 ”,對Redis不同的存儲結構的使用量進行了對比,對此很敢興趣,也發現自己在使用過程中可能存儲誤區,所以就根據自己的業務情況進行了同樣的測試,看看有沒有優化的余地。
1.測試環境和對比項目
C# 4.0 + ServiceStack.Redis 3.9 + Windows Redis 2.6.2
測試同樣數據結構下,測試存儲的Key 的個數100萬:
1) 普通K-V結構存儲
2) 列表結構存儲
3) 獨立哈希結構存儲
4) 多個哈希結構存儲
下面看看簡單的代碼和結果,為了簡單起見,我們使用同一個實體結構和同樣的數據,這個實體是業務中用到的,對字段值進行了模糊處理。
由於上述美團的文章的存儲結構比較簡單,所以我選擇了一個比較接近實際使用的實體結構。7個字段,值類型也基本都有。
static SendScanMsg GetEntity() { return new SendScanMsg() { SN = "測試測試", NN = "測試", SM = "顏色身高7", SC = "123445.888", P = "A59115901094", ST = Convert.ToDateTime("2016-01-18 09:54:53"), RT = Convert.ToDateTime("2016-01-18 10:59:44") }; }
2.單獨的Key-Value存儲
簡單代碼,其中的redis操作經過了封裝,看懂意思即可。K-V存儲可以想象肯定存儲空間是最大的,因為到了一定數量級,Key的長度很關鍵,也很占內存。
public static void TestKeyMemorySingle() { String key = "701183714183_8801_6222"; var model = GetEntity(); Int32 N = 1000000; for (int i = 0; i < N; i++) { MsgRedis.RedisHelper.Item_Set<SendScanMsg>(key + i.ToString(), model, new TimeSpan(10, 0, 0)); } }
結果如下:
# Memory used_memory:317253140 used_memory_human:302.56M used_memory_rss:317253140 used_memory_peak:317253324 used_memory_peak_human:302.56M used_memory_lua:31744 mem_fragmentation_ratio:1.00 mem_allocator:libc
3. List列表結構存儲
看看代碼,List結構只數組類型,理論上也是最節省空間的,因為只有1個Key,看看結果:
public static void TestKeyMemoryList() { String key = "701183714183_8801_6222"; var model = GetEntity(); Int32 N = 1000000; for (int i = 0; i < N; i++) { MsgRedis.RedisHelper.List_Add<SendScanMsg>(key, model); } }
結果如下:
# Memory used_memory:220861160 used_memory_human:210.63M used_memory_rss:220861160 used_memory_peak:410351028 used_memory_peak_human:391.34M used_memory_lua:31744 mem_fragmentation_ratio:1.00 mem_allocator:libc
4.單獨哈希結構
代碼如下,原理和上面類似。實際使用中,哈希結構使用還是很頻繁的,但是也有一些相應的不方便,比如不能針對單個Key設置過期時間,只能對整體的哈希Key設置過期時間,不能分頁獲取等,具體使用根據情況選擇即可。
public static void TestKeyMemoryHash() { String key = "701183714183_8801_6222"; var model = GetEntity(); Int32 N = 1000000; for (int i = 0; i < N; i++) { MsgRedis.RedisHelper.Hash_Set<SendScanMsg>(key,i.ToString(), model); } }
結果如下,可以看到結果比列表多了不多,應該是Key的原因導致的:
# Memory used_memory:253009020 used_memory_human:241.29M used_memory_rss:253009020 used_memory_peak:471307216 used_memory_peak_human:449.47M used_memory_lua:31744 mem_fragmentation_ratio:1.00 mem_allocator:libc
5.多個哈希結構存儲
根據上面那篇文章提供的信息,多個哈希結構存儲要比單個哈希更節省空間。所以我也特意對比了一下,我們將哈希的ID分為用0-100的隊列,取余實現:
public static void TestKeyMemorySplitHash() { String key = "701183714183_8801_6222"; var model = GetEntity(); Int32 N = 1000000; for (int i = 0; i < N; i++) { MsgRedis.RedisHelper.Hash_Set<SendScanMsg>(key+(i%100).ToString(),i.ToString(), model); } }
結果如下,其實和單個哈希相差不大,分析原因,可能和具體的使用實體類的Key有關系。並不是所有的情況都是差距好幾倍。這也是我測試的真正目的,看看真的差距是不是有這么多。
# Memory used_memory:264309588 used_memory_human:252.07M used_memory_rss:264309588 used_memory_peak:266261980 used_memory_peak_human:253.93M used_memory_lua:31744 mem_fragmentation_ratio:1.00 mem_allocator:libc
6.結論
上述結果的直接對比如下圖,由於實際使用的實體結構和上述提到的文章不一樣,所以結果沒有對比性,大家也不能完全迷信我的結果,具體問題,具體分析,我們只能從測試中發現大概的趨勢,至於具體的差距會根據實際情況不同而不同:
Redis使用上對Key的存儲和具體業務要相關,至於是列表還是哈希搞清楚其特點也不難,至於獨立的哈希結構和多個哈希節省空間的問題,大部分差不多,也需要在使用上根據業務划分為好,也不能單獨的為了節省內存空間丟失業務的靈活性。下面簡單說說幾種數據結構的區別:
-
獨立的K-V結構:好處是單個存儲可以靈活設置過期時間,同時同一種數據類型內存占有量會增加;在Redis中結構性不明顯;
-
List結構:List結構好處是可以很靈活的獲取一定范圍的數據,或者分頁,同時也是最省內存的,但是實體獨立查找比較困難;只能對整個List結構設置過期時間;
-
哈希結構:最大的好處是元素的查找效率高,很靈活,但缺點是不能像List那樣按照范圍獲取,也只能設置過期時間;
在經過一段時間的開發后,對數據分析過程中的不同問題和業務采樣合適的結構也有了很深的理解。每種結構的優缺點其實是互補的,只要耐心,仔細分析,其實這幾種結構非常強大。時間進展,至於Redis的使用經驗,個人還有很多不足,如有問題,還請指正。