還記得上次和同事一起去面試候選人時,同事提了一個問題:Redis的大key有什么危害?當時候選人主要作答的角度是一個key的value較大時的情況,比如:
1.內存不均:單value較大時,可能會導致節點之間的內存使用不均勻,間接地影響key的部分和負載不均勻;
2.阻塞請求:redis為單線程,單value較大讀寫需要較長的處理時間,會阻塞后續的請求處理;
3.阻塞網絡:單value較大時會占用服務器網卡較多帶寬,可能會影響該服務器上的其他Redis實例或者應用。
雖說答的是挺好的,但是我又隨之產生了另一個疑惑,如果redis的key較長時,會產生什么樣的影響呢?查了很多文章,說的都不是特別清楚。所以我決心探究一下這個問題。
我們需要知道Redis是如何存儲key和value的:
根結構為RedisServer,其中包含RedisDB(數據庫)。而RedisDB實際上是使用Dict(字典)結構對Redis中的kv進行存儲的。這里的key即字符串,value可以是string/hash/list/set/zset這五種對象之一。

Dict字典結構中,存儲數據的主題為DictHt,即哈希表。而哈希表本質上是一個DictEntry(哈希表節點)的數組,並且使用鏈表法解決哈希沖突問題(關於哈希沖突的解決方法可以參考大佬的文章 解決哈希沖突的常用方法分析)。
所以在這里實際存儲時,key和value都是存儲在DictEntry中的。所以基本上來說,大key和大value帶來的內存不均和網絡IO壓力都是一致的,只是key相較於value還多一個做hashcode和比較的過程(鏈表中進行遍歷比較key),會有更多的內存相關開銷。
結論:
- 大key和大value的危害是一致的:內存不均、阻塞請求、阻塞網絡。
- key由於比value需要做更多的操作如hashcode、鏈表中比較等操作,所以會比value更多一些內存相關開銷。
如何處理?
Redis 大key
Redis使用過程中經常會有各種大key的情況, 比如:
- 單個簡單的key存儲的value很大
- hash, set,zset,list 中存儲過多的元素(以萬為單位)
由於redis是單線程運行的,如果一次操作的value很大會對整個redis的響應時間造成負面影響,所以,業務上能拆則拆,下面舉幾個典型的分拆方案。
業務場景:
即通過hash的方式來存儲每一天用戶訂單次數。那么key = order_20200102, field = order_id, value = 10。那么如果一天有百萬千萬甚至上億訂單的時候,key后面的值是很多,存儲空間也很大,造成所謂的大key。
大key的風險:
1.讀寫大key會導致超時嚴重,甚至阻塞服務。
2.如果刪除大key,DEL命令可能阻塞Redis進程數十秒,使得其他請求阻塞,對應用程序和Redis集群可用性造成嚴重的影響。
redis使用會出現大key的場景:
1.單個簡單key的存儲的value過大;
2.hash、set、zset、list中存儲過多的元素。
解決問題:
1.單個簡單key的存儲的value過大的解決方案:
將大key拆分成對個key-value,使用multiGet方法獲得值,這樣的拆分主要是為了減少單台操作的壓力,而是將壓力平攤到集群各個實例中,降低單台機器的IO操作。
2.hash、set、zset、list中存儲過多的元素的解決方案:
1).類似於第一種場景,使用第一種方案拆分;
2).以hash為例,將原先的hget、hset方法改成(加入固定一個hash桶的數量為10000),先計算field的hash值模取10000,確定該field在哪一個key上。
將大key進行分割,為了均勻分割,可以對field進行hash並通過質數N取余,將余數加到key上面,我們取質數N為997。
那么新的key則可以設置為:
newKey = order_20200102_String.valueOf( Math.abs(order_id.hashcode() % 997) )
field = order_id
value = 10
hset (newKey, field, value) ;
hget(newKey, field)