一文了解如何發(fā)現(xiàn)并解決Redis熱key與大key問(wèn)題
熱Key問(wèn)題
什么是熱key?
熱key是服務(wù)端的常見(jiàn)問(wèn)題,指一段時(shí)間內(nèi)某個(gè)key的訪問(wèn)量遠(yuǎn)遠(yuǎn)超過(guò)其他的key,導(dǎo)致大量訪問(wèn)流量落在某一個(gè)redis實(shí)例中;或者是帶寬使用率集中在特定的key(例如,對(duì)一個(gè)包含2000個(gè)field的hash key每秒發(fā)送大量的hgetall操作請(qǐng)求);又或者是cpu使用時(shí)間占比集中在特定的key(例如,對(duì)一個(gè)包含10000個(gè)field的key每秒發(fā)送大量的zrange操作請(qǐng)求)。
以被請(qǐng)求頻率來(lái)定義是否是熱key,沒(méi)有固定經(jīng)驗(yàn)值。某個(gè)key被高頻訪問(wèn)導(dǎo)致系統(tǒng)穩(wěn)定性變差,都可以定義為熱key。
可能造成的問(wèn)題
- 熱點(diǎn)緩存會(huì)導(dǎo)致流量集中,redis緩存與數(shù)據(jù)庫(kù)被擊穿,從而引發(fā)系統(tǒng)雪崩。詳情可以看《 快速了解緩存穿透與緩存雪崩 》。
- 請(qǐng)求分配不均,存在熱key的節(jié)點(diǎn)面臨較大的訪問(wèn)壓力,可能出現(xiàn)該數(shù)據(jù)分片的連接數(shù)被耗盡甚至宕機(jī)。(即使采取擴(kuò)容也會(huì)對(duì)資源有很大的浪費(fèi))
發(fā)現(xiàn)方法
由于熱key發(fā)生對(duì)系統(tǒng)穩(wěn)定性有巨大危害,所以需要上線前設(shè)立故障預(yù)案、建立監(jiān)控和報(bào)警機(jī)制,以便快速響應(yīng)故障。
- 優(yōu)點(diǎn):簡(jiǎn)單直接。
- 缺點(diǎn):但并不是所有業(yè)務(wù)都能預(yù)估出哪些key是熱key。
- 根據(jù)業(yè)務(wù)經(jīng)驗(yàn),預(yù)估哪些是熱key。
- 在客戶(hù)端收集。在操作redis之前,加上統(tǒng)計(jì)頻次的邏輯,然后將統(tǒng)計(jì)數(shù)據(jù)發(fā)送給一個(gè)聚合計(jì)算的服務(wù)進(jìn)行統(tǒng)計(jì)。
- 優(yōu)點(diǎn):方案簡(jiǎn)單。
- 缺點(diǎn):無(wú)法支持大公司多語(yǔ)言環(huán)境的SDK,或者說(shuō)多語(yǔ)言SDK對(duì)齊比較困難。此外SDK的維護(hù)升級(jí)成本會(huì)很高。
- 在proxy層收集。有些服務(wù)在請(qǐng)求redis之前會(huì)請(qǐng)求一個(gè)proxy服務(wù),這種場(chǎng)景可以使用在proxy層收集熱key數(shù)據(jù),收集機(jī)制類(lèi)似于在客戶(hù)端收集。
- 優(yōu)點(diǎn):方案對(duì)使用方完全透明;沒(méi)有SDK多語(yǔ)言異構(gòu)和升級(jí)成本高的問(wèn)題。(不理解這個(gè)地方的話,可以查看小輝之前的博客《 通用能力抽象選擇SDK組件還是API服務(wù)? 》)
- 缺點(diǎn):并不是所有場(chǎng)景都會(huì)有proxy層。
- redis集群監(jiān)控。如果出現(xiàn)某個(gè)實(shí)例qps傾斜,說(shuō)明可能存在熱key。
- 優(yōu)點(diǎn):不需要額外開(kāi)發(fā)。
- 缺點(diǎn):每次發(fā)生狀況需要人工排查,因?yàn)闊醟ey只是導(dǎo)致qps傾斜的一種可能。
- redis 4.0版本之后熱點(diǎn)key發(fā)現(xiàn)功能。執(zhí)行redis-cli時(shí)加上 –-hotkeys 選項(xiàng)即可。
- 優(yōu)點(diǎn):不需要額外開(kāi)發(fā)。
- 缺點(diǎn):該參數(shù)在執(zhí)行的時(shí)候,如果key比較多,執(zhí)行耗時(shí)會(huì)非常長(zhǎng),由此導(dǎo)致查詢(xún)結(jié)果的實(shí)時(shí)性并不好。
- redis客戶(hù)端使用TCP協(xié)議與服務(wù)端進(jìn)行交互。通過(guò)腳本監(jiān)聽(tīng)端口,解析網(wǎng)絡(luò)包并進(jìn)行分析。
- 優(yōu)點(diǎn):對(duì)原有的業(yè)務(wù)系統(tǒng)沒(méi)有改造。
- 缺點(diǎn):開(kāi)發(fā)成本高,維護(hù)困難,有丟包可能性。
常用的處理方法
如果對(duì)所有熱key進(jìn)行本地緩存,那么本地緩存是否會(huì)過(guò)大,從而影響應(yīng)用程序本身的性能開(kāi)銷(xiāo)。
可能需要保證本地緩存和redis數(shù)據(jù)的一致性。
- 熱key統(tǒng)計(jì)可以使用LFU數(shù)據(jù)結(jié)構(gòu)并結(jié)合上面的發(fā)現(xiàn)方法,將最熱t(yī)opN的key進(jìn)行統(tǒng)計(jì),然后在client端使用本地緩存,從而降低redis集群對(duì)熱key的訪問(wèn)量,但這種方法帶來(lái)兩個(gè)問(wèn)題:
- 將熱key加上前綴或者后綴,把熱key的數(shù)量從1個(gè)變成實(shí)例個(gè)數(shù),利用分片特性將這n個(gè)key分散在不同節(jié)點(diǎn)上,這樣就可以在訪問(wèn)的時(shí)候,采用客戶(hù)端負(fù)載均衡的方式,隨機(jī)選擇一個(gè)key進(jìn)行訪問(wèn),將訪問(wèn)壓力分散到不同的實(shí)例中。這個(gè)方案有個(gè)明顯的缺點(diǎn),就是緩存的維護(hù)成本大:假如有n為100,則更新或者刪除key的時(shí)候需要操作100個(gè)key。
- 利用讀寫(xiě)分離,通過(guò)主從復(fù)制的方式,增加slave節(jié)點(diǎn)來(lái)實(shí)現(xiàn)讀請(qǐng)求的負(fù)載均衡。這個(gè)方案明顯的缺點(diǎn)就是使用機(jī)器硬抗熱key的數(shù)據(jù),資源耗費(fèi)嚴(yán)重;而且引入讀寫(xiě)分離架構(gòu),增加節(jié)點(diǎn)數(shù)量,都會(huì)增加系統(tǒng)的復(fù)雜度降低穩(wěn)定性。
大Key問(wèn)題
什么是大key?
大key是指當(dāng)redis的字符串類(lèi)型占用內(nèi)存過(guò)大或非字符串類(lèi)型元素?cái)?shù)量過(guò)多。
生產(chǎn)環(huán)境中,綜合衡量運(yùn)維和環(huán)境的情況,給大key定義參考值如下:
- string類(lèi)型的key超過(guò)10KB
- hash/set/zset/list等數(shù)據(jù)結(jié)構(gòu)中元素個(gè)數(shù)大于5k/整體占用內(nèi)存大于10MB
不同系統(tǒng)性能條件不同,所以建議這個(gè)標(biāo)準(zhǔn)設(shè)置保守些,以系統(tǒng)穩(wěn)定性為第一考量
可能造成的問(wèn)題
- 內(nèi)存使用不均勻。例如在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)存溢出。
- 響應(yīng)時(shí)間上升、超時(shí)阻塞。由于redis是單線程架構(gòu),操作大key耗時(shí)較長(zhǎng),有可能造成redis阻塞。
- 過(guò)期時(shí)可能阻塞。大key設(shè)定了過(guò)期時(shí)間,當(dāng)過(guò)期時(shí)這個(gè)key會(huì)被刪除。假如redis版本低于4.0沒(méi)有非同步刪除機(jī)制,就會(huì)存在阻塞redis的可能性,并且慢查詢(xún)查不到;同樣,內(nèi)存不足時(shí)的key驅(qū)逐或者是rename一個(gè)大key也會(huì)阻塞redis服務(wù)。長(zhǎng)時(shí)間阻塞主庫(kù),可能會(huì)引發(fā)同步中斷或主從切換。
慢查詢(xún)?yōu)槭裁床椴坏健Ee例,如果請(qǐng)求進(jìn)來(lái)且redis服務(wù)器正在進(jìn)行過(guò)期鍵掃描,需要等待100毫秒。當(dāng)客戶(hù)端設(shè)置的超時(shí)時(shí)間小于100毫秒,那就會(huì)導(dǎo)致連接因?yàn)槌瑫r(shí)而關(guān)閉,就會(huì)造成異常,這些現(xiàn)象并不能從慢查詢(xún)?nèi)罩局胁樵?xún)到(因?yàn)槁樵?xún)只記錄邏輯處理過(guò)程,不包括等待時(shí)間)。
- 網(wǎng)絡(luò)擁塞。例如:一個(gè)大key占用空間是1MB,每秒訪問(wèn)1000次,就有1000MB的流量,可能造成機(jī)器或局域網(wǎng)的帶寬被打滿,同時(shí)波及其他服務(wù)。
發(fā)現(xiàn)方法
使用工具定期掃描,并建立好監(jiān)控和通知機(jī)制。
- 優(yōu)點(diǎn):不阻塞服務(wù)
- 缺點(diǎn):信息較少(只有各類(lèi)型最大的key信息),內(nèi)容不夠精確(例如hash/list/set/zset都是以元素個(gè)數(shù)衡量大key,但實(shí)際上元素個(gè)數(shù)多不代表占用內(nèi)存大)。
- redis-cli --bigkeys 命令。可以用來(lái)找到某個(gè)實(shí)例5種數(shù)據(jù)類(lèi)型(string、hash、list、set、zset)最大的key。
- redis-rdb-tools 工具。redis實(shí)例上執(zhí)行bgsave,然后對(duì)dump出來(lái)的rdb文件進(jìn)行分析。
- 優(yōu)點(diǎn):獲取信息更詳細(xì)
- 缺點(diǎn):需要離線操作,獲取結(jié)果時(shí)間較長(zhǎng)
- Redis4.0之后,新增 memory usage 命令,通過(guò)隨機(jī)抽樣field的方式估算key的大小(樣本越大,循環(huán)次數(shù)越多,計(jì)算結(jié)果越精確,性能消耗也越多)。編寫(xiě)python腳本,利用 scan 和 memory usage 命令,可以在集群低峰的時(shí)候掃描redis,排查大key。
- 優(yōu)點(diǎn):獲取信息較準(zhǔn)確且及時(shí)
- 缺點(diǎn):python腳本需要注意不能影響線上正常服務(wù),設(shè)置好監(jiān)控和熔斷。
常用的處理方法
- 大key非熱key,如果不是必要的信息,可以直接刪除del或者unlink都可以。
如果是redis4.0之前的版本,建議對(duì)于key使用(scan/sscan/hscan/zscan),將大key逐步刪除(ltrim/zremrangebyscore/hdel/srem)。redis4.0之后,直接使用unlink替換del,會(huì)有后臺(tái)線程將大key異步刪除。
- 業(yè)務(wù)拆分,將key的含義更細(xì)粒度化,避免大key出現(xiàn)。
- 數(shù)據(jù)結(jié)構(gòu)上拆分。如果大key是個(gè)大json,可以通過(guò)mset的方式,將這個(gè)key的內(nèi)容打散到各個(gè)實(shí)例中,減小大key對(duì)數(shù)據(jù)量?jī)A斜的影響;如果是大list,可以拆成 list_1,list_2,list_N ;其他數(shù)據(jù)結(jié)構(gòu)同理。(可以考慮增加單獨(dú)key存儲(chǔ)大key被拆分的個(gè)數(shù)或元數(shù)據(jù)信息)
- 在redis沒(méi)有開(kāi)啟非同步刪除機(jī)制的場(chǎng)景下,設(shè)置過(guò)期時(shí)間時(shí),一定要避免大批量鍵同時(shí)過(guò)期的現(xiàn)象,所以如果有這種情況,最好給過(guò)期時(shí)間加個(gè)隨機(jī)范圍,緩解大量鍵同時(shí)過(guò)期,造成客戶(hù)端等待超時(shí)的現(xiàn)象。
- 對(duì)于長(zhǎng)文本,更建議使用文檔型數(shù)據(jù)庫(kù)例如MongoDB等。
- 對(duì)一致性要求不高的場(chǎng)景,嘗試使用客戶(hù)端緩存。(只解決了redis的阻塞問(wèn)題,但機(jī)器或局域網(wǎng)的帶寬問(wèn)題沒(méi)有改善)
- 對(duì)大key的壓縮。相當(dāng)于用cpu資源來(lái)降低網(wǎng)絡(luò)io,其中g(shù)oogle提出的snappy算法較常用。
- 對(duì)于hash等數(shù)據(jù)結(jié)構(gòu),需要注意業(yè)務(wù)是否可以引入定期清理無(wú)效field的機(jī)制。