面試官:大key和大value的危害,如何處理?


還記得上次和同事一起去面試候選人時,同事提了一個問題: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這五種對象之一。

 
image.png

Dict字典結構中,存儲數據的主題為DictHt,即哈希表。而哈希表本質上是一個DictEntry(哈希表節點)的數組,並且使用鏈表法解決哈希沖突問題(關於哈希沖突的解決方法可以參考大佬的文章 解決哈希沖突的常用方法分析)。

所以在這里實際存儲時,key和value都是存儲在DictEntry中的。所以基本上來說,大key和大value帶來的內存不均和網絡IO壓力都是一致的,只是key相較於value還多一個做hashcode和比較的過程(鏈表中進行遍歷比較key),會有更多的內存相關開銷。

結論:

  1. 大key和大value的危害是一致的:內存不均、阻塞請求、阻塞網絡。
  2. key由於比value需要做更多的操作如hashcode、鏈表中比較等操作,所以會比value更多一些內存相關開銷。

如何處理?

Redis 大key

Redis使用過程中經常會有各種大key的情況, 比如:

  1. 單個簡單的key存儲的value很大
  2. 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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM