處理bigkey
bigkey是指key對應(yīng)的value所占的內(nèi)存空間比較大掰茶,例如一個(gè)字符串類型的value可以最大存到512MB,一個(gè)列表類型的value最多可以存儲2^32-1個(gè)元素。如果按照數(shù)據(jù)結(jié)構(gòu)來細(xì)分的話靴迫,一般分為字符串類型bigkey和非字符串類型bigkey惕味。
字符串類型:體現(xiàn)在單個(gè)value值很大,一般認(rèn)為超過10KB就是bigkey玉锌,但這個(gè)值和具體的OPS相關(guān)名挥。
非字符串類型:哈希、列表主守、集合禀倔、有序集合,體現(xiàn)在元素個(gè)數(shù)過多参淫。
bigkey無論是空間復(fù)雜度和時(shí)間復(fù)雜度都不太友好救湖,下面我們將介紹它的危害。
注意:因?yàn)榉亲址當(dāng)?shù)據(jù)結(jié)構(gòu)中涎才,每個(gè)元素實(shí)際上也是一個(gè)字符串鞋既,但這里只討論元素個(gè)數(shù)過多的情況。
-
bigkey的危害
bigkey的危害體現(xiàn)在三個(gè)方面:
內(nèi)存空間不均勻 (平衡):例如在Redis Cluster中耍铜,bigkey會造成節(jié)點(diǎn)的內(nèi)存空間使用不均勻邑闺。
超時(shí)阻塞:由于Redis單線程的特性,操作bigkey比較耗時(shí)棕兼,也就是意味著阻塞Redis可能性增大陡舅。
網(wǎng)絡(luò)阻塞:每次獲取bigkey產(chǎn)生的網(wǎng)絡(luò)流量較大,假設(shè)一個(gè)bigkey為1MB程储,每秒訪問量為1000蹭沛,那么每秒產(chǎn)生1000MB的流量,對于普通的千兆網(wǎng)卡(按照字節(jié)算是128MB/s)的服務(wù)器來說簡直是滅頂之災(zāi)章鲤,而且一般服務(wù)器會采用單機(jī)多實(shí)例的方式來部署摊灭,也就是說一個(gè)bigkey可能會其他實(shí)例造成影響,其后果不堪設(shè)想败徊。
bigkey的存在并不是完全致命的帚呼,如果這個(gè)bigkey存在但是幾乎不被訪問,那么只有內(nèi)存空間不均勻的問題存在皱蹦,相對于另外兩個(gè)問題沒有那么重要緊急煤杀,但是如果bigkey是一個(gè)熱點(diǎn)key(頻繁訪問),那么其帶來的危害不可想象沪哺,所以在實(shí)際開發(fā)和運(yùn)維是一定要密切關(guān)注bigkey的存在沈自。
-
如何發(fā)現(xiàn)
redis-cli --bigkeys可以命令統(tǒng)計(jì)bigkey的分布,但是在生產(chǎn)環(huán)境中辜妓,開發(fā)和運(yùn)維人員更希望自己可以定義bigkey的大小枯途,而且更希望找到真正的bigkey都有哪些忌怎,這樣才可以去定位、解決酪夷、優(yōu)化問題榴啸。判斷一個(gè)key是否為bigkey,只需要執(zhí)行debug object key查看serializedlength屬性即可晚岭,它表示key對應(yīng)的value序列化之后的字節(jié)數(shù)鸥印。
在實(shí)際生產(chǎn)環(huán)境中發(fā)現(xiàn)bigkey的兩種方式如下:
被動收集:許多開發(fā)人員確實(shí)可能對bigkey不了解或重視程序不夠,但是這種bigkey一旦大量訪問坦报,很可能就會帶來命令慢查詢和網(wǎng)卡跑滿問題库说,開發(fā)人員通過對異常的分析能找到異常原因可能是bigkey,這種方式雖然不是被筆者推薦的燎竖,但是實(shí)際生產(chǎn)環(huán)境中卻大量存在璃弄,建議修改Redis客戶端,當(dāng)拋出異常時(shí)打印出所操作的key构回,方便排查bigkey問題夏块。
主動監(jiān)測:scan + debug object:如果懷疑存在bigkey,可以使用scan命令漸進(jìn)的掃描出所有的key纤掸,分別計(jì)算每個(gè)key的serializedlength脐供,找到對應(yīng)bigkey進(jìn)行相應(yīng)的處理和報(bào)警,這種方式是比較推薦的方式借跪。
開發(fā)提示:
如果兼職個(gè)數(shù)比較多政己,scan + debug object會比較慢,可以利用Pipeline機(jī)制完成掏愁。
對于元素個(gè)數(shù)較多的數(shù)據(jù)結(jié)構(gòu)歇由,debug object執(zhí)行速度比較慢,存在阻塞Redis的可能果港。
如果有從節(jié)點(diǎn)沦泌,可以考慮在從節(jié)點(diǎn)上執(zhí)行。
-
如何刪除
當(dāng)發(fā)現(xiàn)Redis中有bigkey并且確認(rèn)要?jiǎng)h除時(shí)辛掠,如何優(yōu)雅地刪除bigkey谢谦?無論是什么數(shù)據(jù)結(jié)構(gòu),del命令都將其刪除萝衩。但是相信通過上面的分析后你一定不會這么做回挽,因?yàn)閯h除bigkey通常來說會阻塞Redis服務(wù)。下面給出一組測試數(shù)據(jù)分別對string猩谊、hash千劈、list、set牌捷、sorted set五種數(shù)據(jù)結(jié)構(gòu)的bigkey進(jìn)行刪除墙牌,bigkey的元素個(gè)數(shù)和每個(gè)元素的大小不盡相同袁梗。
注意:下面測試和服務(wù)器硬件、Redis版本比較相關(guān)憔古,可能在不同的服務(wù)器上執(zhí)行速度不大相同,但是嫩提供一定的參考價(jià)值淋袖。
下表展示了刪除512KB~10MB的字符串類型數(shù)據(jù)所花費(fèi)的時(shí)間鸿市,總體來說由于字符串類型結(jié)構(gòu)相對簡單,刪除速度比較快即碗,但是隨著value值的不斷增大焰情,刪除速度也逐漸變慢。
key類型 512KB 1MB 2MB 5MB 10MB string 0.22ms 0.31ms 0.32ms 0.56ms 1ms 下表展示了非字符串類型的數(shù)據(jù)結(jié)構(gòu)在不同數(shù)量級剥懒、不同元素大小下對bigkey執(zhí)行del命令的時(shí)間内舟,總體上看元素個(gè)數(shù)越多、元素越大初橘,刪除時(shí)間越長验游,相對于字符串類型,這種刪除速度已經(jīng)足夠可以阻塞Redis保檐。
key類型 10萬(8個(gè)字節(jié)) 100萬(8個(gè)字節(jié)) 10萬(16個(gè)字節(jié)) 100萬(16個(gè)字節(jié)) 10萬(128字節(jié)) 100(128字節(jié)) hash 51ms 950ms 58ms 970ms 96ms 2000ms list 23ms 134ms 23ms 138ms 23ms 266ms set 44ms 873ms 58ms 881ms 73ms 1319ms sorted set 51ms 845ms 57ms 859ms 59ms 969ms 從上分析可見耕蝉,除了string類型,其他四種數(shù)據(jù)刪除的速度有可能很慢夜只,這樣增大了阻塞Redis的可能性垒在。既然不能用del命令,那有沒有比較優(yōu)雅的方式進(jìn)行刪除呢扔亥,這時(shí)候就需要將第2章介紹的scan命令的若干類似命令拿出來:sscan场躯、hscan、zscan旅挤。
-
string
對于string類型使用del命令一般不會產(chǎn)生阻塞:
del bigkey
-
hash踢关、list、set谦铃、sorted set
使用hscan命令耘成,每次獲取部分(例如100g個(gè))field-value,再利用hdel刪除每個(gè)field(為了快速可以使用Pipeline)驹闰。
-
-
最佳實(shí)踐思路
由于開發(fā)人員對Redis的理解程度不同瘪菌,在實(shí)際開發(fā)中出現(xiàn)bigkey在所難免,重要的是嘹朗,能通過合理的檢測機(jī)制及時(shí)找到它們师妙,進(jìn)行處理。作為開發(fā)人員在業(yè)務(wù)開發(fā)時(shí)應(yīng)注意不能將Redis簡單暴力的使用屹培,應(yīng)該在數(shù)據(jù)結(jié)構(gòu)的選擇和設(shè)計(jì)上更加合理默穴,例如出現(xiàn)了bigkey怔檩,要思考一下可不可以做一些優(yōu)化(例如拆分?jǐn)?shù)據(jù)結(jié)構(gòu))盡量讓這些bigkey消失在業(yè)務(wù)中,如果bigkey不可避免蓄诽,也要思考一下要不要每次把所有元素都取出來(例如有時(shí)候僅僅需要hmget薛训,而不是hgetall)。最后仑氛,可喜的是乙埃,Redis將在4.0版本支持lazy delete free的模式,那是刪除bigkey不會阻塞Redis锯岖。