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

