redis大key和大value的危害及解決
一、前序
還記得上次和同事一起去面試候選人時(shí),同事提了一個(gè)問題:Redis的大key有什么危害?當(dāng)時(shí)候選人主要作答的角度是一個(gè)key的value較大時(shí)的情況,比如:
- 內(nèi)存不均:單value較大時(shí),可能會(huì)導(dǎo)致節(jié)點(diǎn)之間的內(nèi)存使用不均勻,間接地影響key的部分和負(fù)載不均勻;
- 阻塞請(qǐng)求:redis為單線程,單value較大讀寫需要較長的處理時(shí)間,會(huì)阻塞后續(xù)的請(qǐng)求處理;
- 阻塞網(wǎng)絡(luò):單value較大時(shí)會(huì)占用服務(wù)器網(wǎng)卡較多帶寬,可能會(huì)影響該服務(wù)器上的其他Redis實(shí)例或者應(yīng)用。
雖說答的是挺好的,但是我又隨之產(chǎn)生了另一個(gè)疑惑,如果redis的key較長時(shí),會(huì)產(chǎn)生什么樣的影響呢?查了很多文章,說的都不是特別清楚。所以我決心探究一下這個(gè)問題。
我們需要知道Redis是如何存儲(chǔ)key和value的:
根結(jié)構(gòu)為RedisServer,其中包含RedisDB(數(shù)據(jù)庫)。而RedisDB實(shí)際上是使用Dict(字典)結(jié)構(gòu)對(duì)Redis中的kv進(jìn)行存儲(chǔ)的。這里的key即字符串,value可以是string/hash/list/set/zset這五種對(duì)象之一。
Dict字典結(jié)構(gòu)中,存儲(chǔ)數(shù)據(jù)的主題為DictHt,即哈希表。而哈希表本質(zhì)上是一個(gè)DictEntry(哈希表節(jié)點(diǎn))的數(shù)組,并且使用鏈表法解決哈希沖突問題(關(guān)于哈希沖突的解決方法可以參考大佬的文章 解決哈希沖突的常用方法分析)。
所以在這里實(shí)際存儲(chǔ)時(shí),key和value都是存儲(chǔ)在DictEntry中的。所以基本上來說,大key和大value帶來的內(nèi)存不均和網(wǎng)絡(luò)IO壓力都是一致的,只是key相較于value還多一個(gè)做hashcode和比較的過程(鏈表中進(jìn)行遍歷比較key),會(huì)有更多的內(nèi)存相關(guān)開銷。
二、什么是Redis大key問題
Redis大key問題指的是某個(gè)key對(duì)應(yīng)的value值所占的內(nèi)存空間比較大,導(dǎo)致Redis的性能下降、內(nèi)存不足、數(shù)據(jù)不均衡以及主從同步延遲等問題。
到底多大的數(shù)據(jù)量才算是大key?
沒有固定的判別標(biāo)準(zhǔn),通常認(rèn)為字符串類型的key對(duì)應(yīng)的value值占用空間大于1M,或者集合類型的k元素?cái)?shù)量超過1萬個(gè),就算是大key。
Redis大key問題的定義及評(píng)判準(zhǔn)則并非一成不變,而應(yīng)根據(jù)Redis的實(shí)際運(yùn)用以及業(yè)務(wù)需求來綜合評(píng)估。例如,在高并發(fā)且低延遲的場景中,僅10kb可能就已構(gòu)成大key;然而在低并發(fā)、高容量的環(huán)境下,大key的界限可能在100kb。因此,在設(shè)計(jì)與運(yùn)用Redis時(shí),要依據(jù)業(yè)務(wù)需求與性能指標(biāo)來確立合理的大key閾值。
三、大key帶來的影響
- 內(nèi)存占用過高。大Key占用過多的內(nèi)存空間,可能導(dǎo)致可用內(nèi)存不足,從而觸發(fā)內(nèi)存淘汰策略。在極端情況下,可能導(dǎo)致內(nèi)存耗盡,Redis實(shí)例崩潰,影響系統(tǒng)的穩(wěn)定性。
- 性能下降。大Key會(huì)占用大量內(nèi)存空間,導(dǎo)致內(nèi)存碎片增加,進(jìn)而影響Redis的性能。對(duì)于大Key的操作,如讀取、寫入、刪除等,都會(huì)消耗更多的CPU時(shí)間和內(nèi)存資源,進(jìn)一步降低系統(tǒng)性能。
- 阻塞其他操作。某些對(duì)大Key的操作可能會(huì)導(dǎo)致Redis實(shí)例阻塞。例如,使用DEL命令刪除一個(gè)大Key時(shí),可能會(huì)導(dǎo)致Redis實(shí)例在一段時(shí)間內(nèi)無法響應(yīng)其他客戶端請(qǐng)求,從而影響系統(tǒng)的響應(yīng)時(shí)間和吞吐量。
- 網(wǎng)絡(luò)擁塞。每次獲取大key產(chǎn)生的網(wǎng)絡(luò)流量較大,可能造成機(jī)器或局域網(wǎng)的帶寬被打滿,同時(shí)波及其他服務(wù)。例如:一個(gè)大key占用空間是1MB,每秒訪問1000次,就有1000MB的流量。
- 主從同步延遲。當(dāng)Redis實(shí)例配置了主從同步時(shí),大Key可能導(dǎo)致主從同步延遲。由于大Key占用較多內(nèi)存,同步過程中需要傳輸大量數(shù)據(jù),這會(huì)導(dǎo)致主從之間的網(wǎng)絡(luò)傳輸延遲增加,進(jìn)而影響數(shù)據(jù)一致性。
- 數(shù)據(jù)傾斜。在Redis集群模式中,某個(gè)數(shù)據(jù)分片的內(nèi)存使用率遠(yuǎn)超其他數(shù)據(jù)分片,無法使數(shù)據(jù)分片的內(nèi)存資源達(dá)到均衡。另外也可能造成Redis內(nèi)存達(dá)到maxmemory參數(shù)定義的上限導(dǎo)致重要的key被逐出,甚至引發(fā)內(nèi)存溢出。
四、大key產(chǎn)生的原因
- 業(yè)務(wù)設(shè)計(jì)不合理。這是最常見的原因,不應(yīng)該把大量數(shù)據(jù)存儲(chǔ)在一個(gè)key中,而應(yīng)該分散到多個(gè)key。例如:把全國數(shù)據(jù)按照省行政區(qū)拆分成34個(gè)key,或者按照城市拆分成300個(gè)key,可以進(jìn)一步降低產(chǎn)生大key的概率。
- 沒有預(yù)見value的動(dòng)態(tài)增長問題。如果一直添加value數(shù)據(jù),沒有刪除機(jī)制、過期機(jī)制或者限制數(shù)量,遲早出現(xiàn)大key。例如:微博明星的粉絲列表、熱門評(píng)論等。
- 過期時(shí)間設(shè)置不當(dāng)。如果沒有給某個(gè)key設(shè)置過期時(shí)間,或者過期時(shí)間設(shè)置較長。隨著時(shí)間推移,value數(shù)量快速累積,最終形成大key。
- 程序bug。某些異常情況導(dǎo)致某些key的生命周期超出預(yù)期,或者value數(shù)量異常增長 ,也會(huì)產(chǎn)生大key。
五、怎樣排查大key
SCAN命令
通過使用Redis的SCAN命令,我們可以逐步遍歷數(shù)據(jù)庫中的所有Key。結(jié)合其他命令(如STRLEN、LLEN、SCARD、HLEN等),我們可以識(shí)別出大Key。SCAN命令的優(yōu)勢在于它可以在不阻塞Redis實(shí)例的情況下進(jìn)行遍歷。
bigkeys參數(shù)
使用redis-cli命令客戶端,連接Redis服務(wù)的時(shí)候,加上 —bigkeys 參數(shù),可以掃描每種數(shù)據(jù)類型數(shù)量最大的key。
redis-cli -h 127.0.0.1 -p 6379 —bigkeys
Redis RDB Tools工具
使用開源工具Redis RDB Tools,分析RDB文件,掃描出Redis大key。
例如:輸出占用內(nèi)存大于1kb,排名前3的keys。
rdb —commond memory —bytes 1024 —largest 3 dump.rbd
六、怎么解決大key
拆分成多個(gè)小key。這是最容易想到的辦法,降低單key的大小,讀取可以用mget批量讀取。
數(shù)據(jù)壓縮。使用String類型的時(shí)候,使用壓縮算法減少value大小。或者是使用Hash類型存儲(chǔ),因?yàn)镠ash類型底層使用了壓縮列表數(shù)據(jù)結(jié)構(gòu)。
設(shè)置合理的過期時(shí)間。為每個(gè)key設(shè)置過期時(shí)間,并設(shè)置合理的過期時(shí)間,以便在數(shù)據(jù)失效后自動(dòng)清理,避免長時(shí)間累積的大Key問題。
啟用內(nèi)存淘汰策略。啟用Redis的內(nèi)存淘汰策略,例如LRU(Least Recently Used,最近最少使用),以便在內(nèi)存不足時(shí)自動(dòng)淘汰最近最少使用的數(shù)據(jù),防止大Key長時(shí)間占用內(nèi)存。
數(shù)據(jù)分片。例如使用Redis Cluster將數(shù)據(jù)分散到多個(gè)Redis實(shí)例,以減輕單個(gè)實(shí)例的負(fù)擔(dān),降低大Key問題的風(fēng)險(xiǎn)。
刪除大key。使用UNLINK命令刪除大key,UNLINK命令是DEL命令的異步版本,它可以在后臺(tái)刪除Key,避免阻塞Redis實(shí)例。
七、Redis 大key如何處理?
Redis使用過程中經(jīng)常會(huì)有各種大key的情況, 比如:
單個(gè)簡單的key存儲(chǔ)的value很大
hash, set,zset,list 中存儲(chǔ)過多的元素(以萬為單位)
由于redis是單線程運(yùn)行的,如果一次操作的value很大會(huì)對(duì)整個(gè)redis的響應(yīng)時(shí)間造成負(fù)面影響,所以,業(yè)務(wù)上能拆則拆,下面舉幾個(gè)典型的分拆方案。
業(yè)務(wù)場景:
即通過hash的方式來存儲(chǔ)每一天用戶訂單次數(shù)。那么key = order_20200102, field = order_id, value = 10。那么如果一天有百萬千萬甚至上億訂單的時(shí)候,key后面的值是很多,存儲(chǔ)空間也很大,造成所謂的大key。
大key的風(fēng)險(xiǎn):
讀寫大key會(huì)導(dǎo)致超時(shí)嚴(yán)重,甚至阻塞服務(wù)。
如果刪除大key,DEL命令可能阻塞Redis進(jìn)程數(shù)十秒,使得其他請(qǐng)求阻塞,對(duì)應(yīng)用程序和Redis集群可用性造成嚴(yán)重的影響。
redis使用會(huì)出現(xiàn)大key的場景:
- 單個(gè)簡單key的存儲(chǔ)的value過大;
- hash、set、zset、list中存儲(chǔ)過多的元素。
解決問題:
- 單個(gè)簡單key的存儲(chǔ)的value過大的解決方案:
將大key拆分成對(duì)個(gè)key-value,使用multiGet方法獲得值,這樣的拆分主要是為了減少單臺(tái)操作的壓力,而是將壓力平攤到集群各個(gè)實(shí)例中,降低單臺(tái)機(jī)器的IO操作。
- hash、set、zset、list中存儲(chǔ)過多的元素的解決方案:
1).類似于第一種場景,使用第一種方案拆分;
2).以hash為例,將原先的hget、hset方法改成(加入固定一個(gè)hash桶的數(shù)量為10000),先計(jì)算field的hash值模取10000,確定該field在哪一個(gè)key上。
將大key進(jìn)行分割,為了均勻分割,可以對(duì)field進(jìn)行hash并通過質(zhì)數(shù)N取余,將余數(shù)加到key上面,我們?nèi)≠|(zhì)數(shù)N為997。
那么新的key則可以設(shè)置為:
newKey = order_20200102_String.valueOf( Math.abs(order_id.hashcode() % 997) ) field = order_id value = 10 hset (newKey, field, value) ; hget(newKey, field)
八、 大value數(shù)據(jù)是什么,會(huì)有怎樣的問題?
當(dāng)String類型的數(shù)據(jù)>10K,list、hash、set、sort set中元素個(gè)數(shù)超過1000時(shí)就可以被稱為大value,當(dāng)超過100K,或集合元素個(gè)數(shù)超過10000時(shí)可以被稱為是超大value。大value最直接的影響就是有可能造成機(jī)器內(nèi)存不足,就是數(shù)據(jù)傾斜;同時(shí)因?yàn)閞edis數(shù)據(jù)處理是單線程的,當(dāng)value過大時(shí),處理起來響應(yīng)時(shí)間也會(huì)變慢。 常見的例子有:參與人數(shù)很多的蓋樓活動(dòng)或者很活躍的群聊消息列表等
九、怎么處理Redis大value?
大value的處理方式還是結(jié)合業(yè)務(wù),對(duì)其進(jìn)行拆分,將其數(shù)據(jù)分布在各個(gè)redis節(jié)點(diǎn)中,將操作壓力平攤開,防止對(duì)單個(gè)實(shí)例IO或內(nèi)存影響過大。
簡單說一下 熱點(diǎn)數(shù)據(jù)和大value的拆分,如果它是一個(gè)list、 set集合類型,比如原來的 為key value,value為list為拆為 list1 、list2、list3,那么新的key為 key+hash(list1)%10000 得到新的key,再對(duì)對(duì)應(yīng)數(shù)據(jù)value進(jìn)行set或get操作
如果是一個(gè)對(duì)象的json字符串,可以考慮將該對(duì)象的不同屬性映射到不同hash槽從而分布在不同redis節(jié)點(diǎn)中;或者將不同屬性拆分,利用hash結(jié)構(gòu)進(jìn)行存儲(chǔ),從而每次處理時(shí)僅獲取一部分?jǐn)?shù)據(jù)
十、總結(jié)
大key和大value的危害是一致的:內(nèi)存不均、阻塞請(qǐng)求、阻塞網(wǎng)絡(luò)。
key由于比value需要做更多的操作如hashcode、鏈表中比較等操作,所以會(huì)比value更多一些內(nèi)存相關(guān)開銷。
本文主要詳細(xì)介紹了大Key產(chǎn)生的原因、影響、檢測方法和解決方案。通過優(yōu)化數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)、設(shè)定合理的數(shù)據(jù)過期策略、優(yōu)化系統(tǒng)架構(gòu)和配置,以及漸進(jìn)式刪除大Key等方法,我們可以有效地解決和預(yù)防大Key問題,從而提高Redis系統(tǒng)的穩(wěn)定性和性能。
到此這篇關(guān)于redis大key和大value的危害及解決的文章就介紹到這了,更多相關(guān)redis大key和大value內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解redis腳本命令執(zhí)行問題(redis.call)
這篇文章主要介紹了redis腳本命令執(zhí)行問題(redis.call),分別介紹了redis-cli命令行中執(zhí)行及l(fā)inux命令行中執(zhí)行問題,本文給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-03-03分布式鎖為什么要選擇Zookeeper而不是Redis?看完這篇你就明白了
Zookeeper的機(jī)制可以保證分布式鎖實(shí)現(xiàn)業(yè)務(wù)代碼簡單,成本低,Redis如果要解決分布式鎖的問題,對(duì)于一些復(fù)雜的情況,很難解決,成本較高,這篇文章重點(diǎn)給大家介紹分布式鎖選擇Zookeeper 而不是Redis的理由,一起看看吧2021-05-05redis實(shí)現(xiàn)sentinel哨兵架構(gòu)的方法
哨兵是一個(gè)分布式系統(tǒng),可以在一個(gè)架構(gòu)中運(yùn)行多個(gè)哨兵(sentinel) 進(jìn)程,這些進(jìn)程使用流言協(xié)議(gossip protocols)來接收關(guān)于Master主服務(wù)器是否下線的信息,這篇文章主要介紹了redis實(shí)現(xiàn)sentinel哨兵架構(gòu),需要的朋友可以參考下2022-11-11一文詳解Redis在Ubuntu系統(tǒng)上的安裝步驟
安裝redis在Ubuntu上有多種方法,下面這篇文章主要給大家介紹了關(guān)于Redis在Ubuntu系統(tǒng)上安裝的相關(guān)資料,文中通過圖文以及代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07基于 Redis 的 JWT令牌失效處理方案(實(shí)現(xiàn)步驟)
當(dāng)用戶登錄狀態(tài)到登出狀態(tài)時(shí),對(duì)應(yīng)的JWT的令牌需要設(shè)置為失效狀態(tài),這時(shí)可以使用基于Redis 的黑名單方案來實(shí)現(xiàn)JWT令牌失效,本文給大家分享基于 Redis 的 JWT令牌失效處理方案,感興趣的朋友一起看看吧2024-03-03redis在Linux系統(tǒng)下的環(huán)境配置和redis的全局命令大全
在Linux系統(tǒng)中我們經(jīng)常使用Redis作為高性能的緩存數(shù)據(jù)庫,然而有時(shí)候我們需要在系統(tǒng)中多個(gè)地方使用Redis命令,這就需要將Redis的全局命令設(shè)置好,這篇文章主要給大家介紹了關(guān)于redis在Linux系統(tǒng)下的環(huán)境配置和redis的全局命令大全的相關(guān)資料,需要的朋友可以參考下2024-05-05Redis使用ZSET實(shí)現(xiàn)消息隊(duì)列使用小結(jié)
這篇文章主要介紹了Redis使用ZSET實(shí)現(xiàn)消息隊(duì)列使用總結(jié),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03