詳解Redis中的BigKey如何發(fā)現(xiàn)和處理
什么是BigKey?
通常來(lái)說(shuō),如果一個(gè)鍵值對(duì)大于一定閾值(例如 1MB),它就可以被認(rèn)為是一個(gè)大鍵或 bigkey。
大鍵的存在通常被認(rèn)為是不好的,主要原因:
1.占用大量?jī)?nèi)存,可能導(dǎo)致內(nèi)存不足
2.增加內(nèi)存碎片,降低內(nèi)存利用效率
3.增加SAVE和復(fù)制時(shí)間
4.增加AOF重寫(xiě)時(shí)間
5.壓縮列表元素過(guò)多,降低查找效率
所以在使用 Redis 時(shí),應(yīng)該盡量避免 bigkey 的產(chǎn)生。
常見(jiàn)的bigkey原因:
1.一個(gè) Hash 類(lèi)型鍵值對(duì)字段過(guò)多
2.一個(gè) Set 類(lèi)型包含了過(guò)多的成員
3.一個(gè) List 類(lèi)型包含了過(guò)多的元素
4.一個(gè) Bitmaps 類(lèi)型映射了過(guò)大的內(nèi)容
5.一個(gè) Zset 類(lèi)型包含了過(guò)多的成員
一般建議 bigkey 的大小控制在 1MB 以?xún)?nèi)為宜。
BigKey危害?
Redis 中存在的BigKey會(huì)帶來(lái)以下幾個(gè)方面的危害:
占用大量?jī)?nèi)存空間
BigKey由于值過(guò)于龐大,會(huì)占用Redis sehr大比例的內(nèi)存,從而對(duì)Redis的內(nèi)存使用造成浪費(fèi)和壓力。
阻塞服務(wù)器進(jìn)程
當(dāng)需要對(duì)BigKey進(jìn)行操作如刪除、更改值時(shí),這些操作需要花費(fèi)較長(zhǎng)時(shí)間,會(huì)造成Redis主進(jìn)程阻塞,影響Redis的正常工作。
加長(zhǎng)持久化時(shí)間
Redis需要進(jìn)行持久化將數(shù)據(jù)寫(xiě)入磁盤(pán)保存,BigKey會(huì)因其數(shù)據(jù)量大而導(dǎo)致持久化過(guò)程非常緩慢。
延長(zhǎng)復(fù)制時(shí)間
當(dāng)Redis進(jìn)行主從復(fù)制時(shí),同步BigKey也會(huì)非常慢,可能導(dǎo)致從服務(wù)器的數(shù)據(jù)同步效率下降。
增加內(nèi)存碎片
BigKey的大值會(huì)導(dǎo)致Redis內(nèi)存中產(chǎn)生大量不連續(xù)的碎片,降低內(nèi)存利用效率。
加重AOF重寫(xiě)壓力
BigKey會(huì)導(dǎo)致AOF文件過(guò)大,當(dāng)Redis進(jìn)行AOF重寫(xiě)時(shí),需要處理大量數(shù)據(jù),加重服務(wù)器壓力。
降低查找效率
對(duì)于一些集合性的數(shù)據(jù)結(jié)構(gòu)如Hash、List等,BigKey中的數(shù)據(jù)量龐大,會(huì)降低查找效率。
所以,對(duì)于Redis來(lái)說(shuō),發(fā)現(xiàn)并解決BigKey問(wèn)題是非常重要的。
如何發(fā)現(xiàn)BigKey?
在Redis中,可以通過(guò)以下幾種方式發(fā)現(xiàn)BigKey:
info命令
使用Redis的info命令,查看keyspace部分,觀察biggest_key_size的大小,當(dāng)biggest_key_size過(guò)大時(shí),說(shuō)明存在BigKey。
可以通過(guò)analyze命令的keyspace部分來(lái)觀察biggest_key_size,判斷是否存在BigKey。例如:
127.0.0.1:6379> info keyspace # Keyspace db0:keys=5,expires=0,avg_ttl=0 db1:keys=8,expires=0,avg_ttl=0 db2:keys=4,expires=0,avg_ttl=0 db3:keys=5,expires=0,avg_ttl=0 db4:keys=4,expires=0,avg_ttl=0 db5:keys=5,expires=0,avg_ttl=0 db6:keys=5,expires=0,avg_ttl=0 db7:keys=4,expires=0,avg_ttl=0 db8:keys=4,expires=0,avg_ttl=0 db9:keys=4,expires=0,avg_ttl=0 db10:keys=5,expires=0,avg_ttl=0 db11:keys=5,expires=0,avg_ttl=0 db12:keys=5,expires=0,avg_ttl=0 db13:keys=4,expires=0,avg_ttl=0 db14:keys=4,expires=0,avg_ttl=0 db15:keys=4,expires=0,avg_ttl=0 # ... 略 biggest_key_size:8192
從上面的biggest_key_size大小可以看出,目前存在value超過(guò)8KB的大鍵。
scan命令
使用scan命令迭代Redis中的鍵,同時(shí)利用Redis的debug object命令查看key對(duì)應(yīng)的value大小,如果超過(guò)閾值即判定為BigKey。
可以通過(guò)SCAN命令迭代Redis中的鍵,并配合DEBUG OBJECT命令判斷鍵值大小,來(lái)發(fā)現(xiàn)BigKey。例如:
127.0.0.1:6379> scan 0 match * count 10 1) "17" 2) 1) "key:1" 2) "key:2" 3) "key:3" 4) "key:4" 5) "bigkey" 127.0.0.1:6379> debug object bigkey Value at:0x7fdafb0258e0 refcount:1 encoding:raw serializedlength:1001927
上面先通過(guò)SCAN命令掃描出鍵,然后使用DEBUG OBJECT判斷bigkey的值大小,結(jié)果顯示serializedlength超過(guò)1MB。所以這是一個(gè)BigKey。我們可以編寫(xiě)一個(gè)循環(huán)使用SCAN的程序,設(shè)置一個(gè)大小閾值,所有值超過(guò)閾值的鍵都記錄下來(lái),從而掃描出所有BigKey。相比直接遍歷鍵空間,SCAN實(shí)現(xiàn)漸進(jìn)式掃描,可以避免命令阻塞,更安全的發(fā)現(xiàn)BigKey。
Redis-cli
使用redis-cli工具的–bigkeys參數(shù),該參數(shù)會(huì)掃描Redis中的鍵并返回所有大小超過(guò)指定閾值的BigKey。
第三方工具
使用一些第三方Redis可視化管理工具,如Redis Enterprise、Astra等,這些工具提供了BigKey檢測(cè)功能。
日志監(jiān)控
分析Redis日志,關(guān)注slave緩慢或者持久化被block的情況,這可能與BigKey有關(guān)。
定期主動(dòng)掃描
可以通過(guò)腳本等定期主動(dòng)掃描Redis鍵值,當(dāng)鍵值超過(guò)閾值則記錄為BigKey。
下面寫(xiě)一個(gè)腳本示例
import redis import time # Redis連接 redis_client = redis.Redis(host='localhost', port=6379) # 大鍵閾值 - 100MB BIG_KEY_THRESHOLD = 100 * 1024 * 1024 # 掃描間隔 - 1小時(shí) SCAN_INTERVAL = 3600 def scan_big_keys(): cursor = '0' big_keys = [] while cursor != 0: cursor, keys = redis_client.scan(cursor=cursor, count=100) for key in keys: size = redis_client.debug_object(key)['serializedlength'] if size > BIG_KEY_THRESHOLD: big_keys.append({'key': key, 'size': size}) return big_keys while True: big_keys = scan_big_keys() if big_keys: print(f'Found {len(big_keys)} big keys') for bk in big_keys: print(f' {bk["key"]} {bk["size"]}') time.sleep(SCAN_INTERVAL)
主要步驟包括:
? 1.使用SCAN命令漸進(jìn)式掃描鍵空間
? 2.調(diào)用DEBUG OBJECT命令獲取鍵值大小
? 3.比較大小判斷是否為大鍵
? 4.定期循環(huán)掃描這個(gè)腳本可以靈活調(diào)整大鍵閾值、掃描間隔等參數(shù)。
你可以將它部署為持續(xù)運(yùn)行的任務(wù),自動(dòng)發(fā)現(xiàn)Redis中的大鍵。
綜合利用上述各種方式,可以有效發(fā)現(xiàn)Redis中存在的BigKey,作為后續(xù)優(yōu)化的依據(jù)。
如何刪除BigKey?
在Redis中刪除BigKey可以通過(guò)以下幾種方法:
DEL命令
直接使用DEL命令刪除鍵即可快速刪除單個(gè)BigKey,但是這會(huì)導(dǎo)致數(shù)據(jù)丟失。
DEL big_key
重新設(shè)計(jì)鍵
如果BigKey是由于鍵設(shè)計(jì)不當(dāng)導(dǎo)致的,那么可以重新設(shè)計(jì)鍵結(jié)構(gòu),拆分BigKey。
例如將原來(lái)的一個(gè)Hash BigKey拆分為多個(gè)Hash小Key。
使用UNLINK
UNLINK可以異步刪除鍵,不會(huì)堵塞服務(wù)器。但也存在數(shù)據(jù)丟失風(fēng)險(xiǎn)。
UNLINK big_key
配合事務(wù)操作
可以先用MULTI開(kāi)啟事務(wù),再使用GET、DEL等命令處理BigKey,最后用EXEC提交事務(wù)。這樣可以避免阻塞服務(wù)器。
分段刪除
對(duì)于List、Hash等結(jié)構(gòu)的BigKey,可以通過(guò)區(qū)間查找、分段刪除的方式進(jìn)行分批刪除,避免一次性刪除耗時(shí)太長(zhǎng)。
使用SCAN命令
可以通過(guò)SCAN命令漸進(jìn)式地掃描并刪除BigKey。
熱重啟
可以考慮停止Redis服務(wù),并刪除持久化文件(rdb、aof),然后重啟,這樣可以直接清除所有BigKey,但會(huì)損失全部數(shù)據(jù)。
可以根據(jù)實(shí)際情況選擇最佳方案刪除BigKey。
Hash類(lèi)型Bigkey如何處理?
對(duì)于Redis中的Hash類(lèi)型Bigkey,可以通過(guò)以下幾種方式進(jìn)行優(yōu)化:
拆分Bigkey
最直接的方法是將一個(gè)大的Hash Key拆分為多個(gè)小的Hash Key,防止單個(gè)鍵值對(duì)過(guò)大。例如
將user:{uid} 拆分為 user:{uid}:info、user:{uid}:data等。
使用Hash數(shù)據(jù)結(jié)構(gòu)
Redis的Hash相比字符串可以大幅度減少內(nèi)存使用??梢钥紤]將字符串值轉(zhuǎn)換為Hash結(jié)構(gòu),字段由字符串改為Hash。
海量計(jì)數(shù)器轉(zhuǎn)換為Bitmaps
如果Hash中的字段是簡(jiǎn)單的計(jì)數(shù)器,可以考慮用Bitmaps替代,由于Bitmaps有壓縮特性,可以減少內(nèi)存使用。
采用更緊湊的編碼
對(duì)于Hash的值,如果是數(shù)字,可以選擇更緊湊的編碼方式,如整形float編碼轉(zhuǎn)為整數(shù)int編碼。
控制字段的數(shù)量
對(duì)Hash字段數(shù)量進(jìn)行限制,避免單個(gè)Hash過(guò)多字段,導(dǎo)致結(jié)構(gòu)過(guò)大。
使用內(nèi)存優(yōu)化參數(shù)
調(diào)整Redis的ziplist、hash-max-ziplist-value等參數(shù),優(yōu)化內(nèi)存使用。
redis>config get hash-max-ziplist-entries 1) "hash-max-ziplist-entries" 2) "512" --默認(rèn)原來(lái)是512 redis>config set hash-max-ziplist-entries 1000 "OK" redis>config get hash-max-ziplist-entries 1) "hash-max-ziplist-entries" 2) "1000" --調(diào)整為1000,但是不建議調(diào)整超過(guò)1000
海量小值分離
如果Hash中存在大量小值字段,可以將這些小值字段拆分出去,單獨(dú)用一個(gè)Hash Key存儲(chǔ)。
假設(shè)我們有一個(gè) user:{uid} 的 hash 類(lèi)型 bigkey,里面存儲(chǔ)了用戶(hù)的名稱(chēng)、年齡、手機(jī)號(hào)等信息。其中手機(jī)號(hào)字段的值都是數(shù)字且較小。
我們可以這樣進(jìn)行海量小值的分離:
為手機(jī)號(hào)創(chuàng)建新的 hash key
user:{uid}:phone
遍歷 user:{uid} 的 hash,提取所有手機(jī)號(hào)字段和值,存入 user:{uid}:phone
HGETALL user:{uid} # 遍歷獲取所有字段 ... HSET user:{uid}:phone {phone_field} {phone_value} # 存儲(chǔ)到新key
從 user:{uid} 刪除這些手機(jī)號(hào)字段
HDEL user:{uid} {phone_field_1} {phone_field_2} ... # 刪除手機(jī)號(hào)字段
這樣我們就將海量的手機(jī)號(hào)小值從大key中分離出來(lái),單獨(dú)用一個(gè) hash 存儲(chǔ)。
分離后的優(yōu)點(diǎn):
1.原來(lái)的 user:{uid} 鍵值對(duì)會(huì)縮小,不再存在大量小值
2.由于是小值,在新 key 中可以使用更緊湊的內(nèi)存編碼存儲(chǔ)
3.查找手機(jī)號(hào)等小值時(shí),只需要操作 user:{uid}:phone 即可,避免遍歷全量大key
綜合使用這些優(yōu)化策略,可以有效減小Hash類(lèi)型Bigkey的內(nèi)存占用。
寫(xiě)在最后
以上就是詳解Redis中的BigKey如何發(fā)現(xiàn)和處理的詳細(xì)內(nèi)容,更多關(guān)于Redis BigKey發(fā)現(xiàn)和處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用 Redis 流實(shí)現(xiàn)消息隊(duì)列的代碼
這篇文章主要介紹了使用 Redis 流實(shí)現(xiàn)消息隊(duì)列,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11超強(qiáng)、超詳細(xì)Redis數(shù)據(jù)庫(kù)入門(mén)教程
這篇文章主要介紹了超強(qiáng)、超詳細(xì)Redis入門(mén)教程,本文詳細(xì)介紹了Redis數(shù)據(jù)庫(kù)各個(gè)方面的知識(shí),需要的朋友可以參考下2014-10-10詳解Spring?Boot?訪問(wèn)Redis的三種方式
這篇文章主要介紹了Spring?Boot?訪問(wèn)Redis的三種方式,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12Redis?Hash序列化存儲(chǔ)的問(wèn)題及解決方案
這篇文章主要介紹了Redis?Hash序列化存儲(chǔ)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Redis從單點(diǎn)到集群部署模式(單機(jī)模式?主從模式?哨兵模式)
這篇文章主要為大家介紹了Redis從單點(diǎn)集群部署模式(單機(jī)模式?主從模式?哨兵模式)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11redis緩存與數(shù)據(jù)庫(kù)一致性的問(wèn)題及解決
這篇文章主要介紹了redis緩存與數(shù)據(jù)庫(kù)一致性的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06