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