在Redis的運維使用過程中你遇到過那些問題,又是如何解決的呢?本文收集了一些Redis的常見問題以及解決方案,與大家一同探討。
碼字不易,歡迎大家轉載,煩請注明出處;謝謝配合
你的Redis有bigkeys嗎?
什么是bigkeys
bigkeys是指key不恰當設定,抑或是key對應的value值占用內存空間過大;具體表現為以下幾種情形:
- key值不恰當設定(比較少見),key設定冗長
- String類型 value值長度過大
- Hash,List,Set,Zset 包含元素個數過多
bigkeys有什么危害
為什么我們必須警惕bigkey呢?其實bigkey主要有以下幾個方面的危害:
- 內存使用不均勻,例如:在Redis-Cluster模式中,bigkey會造成節點內存使用不均勻。
- 超時阻塞,由於Redis是單線程架構,操作bigkey耗時較長,有可能造成Redis阻塞。
- 網絡擁阻,例如:一個bigkey占用空間是1M,每秒訪問1000次,將造成1000M的流量,可能造成打滿機器帶寬。
當然,如果bigkey訪問頻率不高,也僅會造成節點間內存使用不均;而當bigkey訪問頻繁時,其帶來的影響是不可想象的,所以日常在開發運維的過程中應該警惕bigkey的存在。
如何找到bigkeys
了解到bigkey危害,我們該如何發現bigkeys呢?
Redis在設計之初就考慮到bigkeys的問題,我們可以使用 redis-cli --bigkeys 來發現bigkeys的分布情況;之后你如果想進一步了解bigkeys的具體情況可以使用 debug object <key> 來確定該key的具體信息。參考以下示例:
利用redis-cli --bigkeys找到bigkey,具體生產環境執行時強烈建議在從節點實行,如果擔心OPS太高,可以使用 -i 0.1 ,表示每100條scan命令休眠0.1秒;其實該命令實現的原理就是利用我們常用的scan + type + strlen/hlen/llen/scard/zcard 命令實現的,具體可以從redis-cli.c的源碼中探尋。
[root@VM_0_16_centos src]# redis-cli -p 6380 --bigkeys # Scanning the entire keyspace to find biggest keys as well as # average sizes per key type. You can use -i 0.1 to sleep 0.1 sec # per 100 SCAN commands (not usually needed). [00.00%] Biggest string found so far 'h' with 1 bytes [00.00%] Biggest string found so far 'hello' with 105 bytes [00.00%] Biggest string found so far 'heml' with 1434 bytes -------- summary ------- Sampled 3 keys in the keyspace! Total key length in bytes is 10 (avg len 3.33) Biggest string found 'html' has 1434 bytes 0 lists with 0 items (00.00% of keys, avg size 0.00) 0 hashs with 0 fields (00.00% of keys, avg size 0.00) 3 strings with 1540 bytes (100.00% of keys, avg size 513.33) 0 streams with 0 entries (00.00% of keys, avg size 0.00) 0 sets with 0 members (00.00% of keys, avg size 0.00) 0 zsets with 0 members (00.00% of keys, avg size 0.00)
執行結果是發現String類型的"html"為bigkey,我們緊接着來了解"html"的具體信息;使用
debug object <key> 命令,還有如果是對於元素個數較多的數據結構,該命令可能會阻塞redis實例,所以強烈建議在從節點執行
[root@VM_0_16_centos src]# redis-cli -p 6380 127.0.0.1:6379> debug object html Value at:0x7f0b13a665c0 refcount:1 encoding:raw serializedlength:251 lru:12181323 lru_seconds_idle:229 127.0.0.1:6379> strlen html (integer) 1434
我們發現key為"html"的String類型的value長達1434個字節,以上便是演示查找bigkeys的過程;除了以上方式我們可以在bigkeys影響redis正常提供服務之前,通過 scan + debug object 對懷疑的bigkeys進行逐個檢查。
當然你如果擔心執行相關命令會對正式環境有一定的影響,你也可以通過對RDB進行備份,然后根據RDB文件的結構,對RDB中的數據進行逐個分析同樣的可以找到bigkey,不過這種方式的開發成本會有些高;使用者可以依據自己的實際情況來酌情判斷。
如何處理bigkeys
經過一番折騰,我們終於找到bigkeys了,那么我們應該如何處理它呢?
在Redis4.0之前版本,由於DEL命令是同步刪除的,針對String類型的bigkeys確實可以使用DEL命令,刪除速度相對較快,一般不會阻塞redis;然而對於元素個數較多的數據結構,使用DEL命令來刪除可能會阻塞redis實例;針對 Hash 結構,我們可以利用 HSCAN + HDEL 刪除元素的成員,成員刪除之后再利用 DEL 刪除key;其余數據類似都是漸進的方式先刪除成員,再刪除key。
Redis4.0版本之后則支持了Lazy Delete Free模式,你可以使用 UNLINK 命令來刪除bigkeys,它的實現是異步的,具體可以從redis-cli.c的源碼中探尋,你需要先確認打開了lazyfree相關配置。
bigkeys總結
bigkeys的表現形式是內存分配不均;頻繁操作的實際影響是有可能造成超時阻塞,網絡擁阻;解決思路是事前監控,事中找到bigkeys,並通過正確的方式刪除bigkeys。
你的Redis有hotkeys嗎?
什么是hotkeys?
hotkeys是在Redis實例中某些key的操作頻次遠高於其他key,那么這些被頻繁操作的熱點key我們就稱之為hotkeys。
hotkeys有什么危害?
hotkeys有什么危害呢?以Redis-Cluster模式為例,存在hotkeys的節點,將面臨以下挑戰:
- 請求分配不均,存在hotkeys的節點面臨較大的訪問壓力
- 緩存擊穿,hotkeys過期時,大量請求將直接導向DB
- 緩存雪崩,擊垮存在hotkeys的節點,導致不能正常提供服務
如何發現hotkeys呢?
Redis4.0之后客戶端提供了hotkeys發現的相關命令,我們可以通過 redis-cli --hotkeys 來發現hotkeys;
Redis4.0之前我們也可以通過客戶端,代理端,服務端,機器端等多個方面來發現hotkeys:
- 在客戶端建立全局字典表,對key和調用次數進行統計;缺點:侵入客戶端
- 如果你的集群是通過proxy + redis 的方式搭建的,那你可以很方便的從代理端對key和調用次數進行監控;缺點:限制代理模式的集群
- 在服務端可以利用 monitor,對服務端接收的請求進行監控;缺點:侵入服務端,在高並發情況下會使內存暴增,適合短時間使用
- 如果你不想侵入服務端與客戶端,可以對服務端接收的請求進行抓包,分析以及監控;例如使用ELK(Elasticsearch + Logstach + kinbana)用packetbeat進行抓包。
如何處理hotkeys?
我們了解到hotkeys的危害,並可以通過技術手段找到hotkeys以后,我們該怎么對系統做優化呢?
首先針對hotkeys過期,面臨的重建問題,可以使用以下有效手段來盡可能的減少key重建的過程:
- 設置互斥鎖,保證由一個線程完成熱點key的重建,避免大量的請求直接導向DB
- "永不過期",將hotkeys的過期時間設置較長的時間,或者永不過期;等待hotkeys觸發的熱點事件過去后再考慮過期。
針對Redis集群的優化,包括但不限於以下幾種方式:
- hotkeys表現就是請求分配不均;我們可以以此為出發點,來想辦法使請求盡可能的分布平均;例如:利用<hotkeys_n,value> ,n為隨機數,盡可能的多個實例都有該數據,在訪問時在n的范圍內取隨機數以此來分攤請求;此方式需要一定的代碼改造;
- 本地緩存,此方式需要對熱點信息有預知,例如:電商產品大促,熱點產生在可以預知的范圍內,便可以考慮此方式;
- 集群的熱點數據的節點的擴容,此方式原理同第一種方式相同,不同點在於不需要對代碼進行改造,而是直接增加hotkeys對應節點的數據副本,使多個節點都具備提供該數據的讀取能力,以此來均衡請求。
hotkeys總結
hotkeys的表現形式是請求的分配不均,問題惡化將導致Redis集群請求傾斜,甚至集群雪崩,我們可以通過多種途徑來均衡請求,避免單個節點過熱;如果hotkeys的超時實現過短,可能會導致大量請求涌入到DB,並發重建key,可以通過合理的鎖機制或者設置合理的超時時間來避免。
Redis緩存穿透
什么是緩存穿透
緩存穿透是大量請求的key在緩存中沒有,直接請求到DB,使緩存失去保護數據庫的作用;例如:黑客刻意構建大量緩存中沒有的key,導致每次處理請求都需要去訪問數據庫。
正常緩存處理流程


解決緩存穿透
通過以上流程圖我們對緩存穿透有了一定的了解,那該如何解決此類問題呢?通常解決的方式有兩種:
(1) 對空值進行緩存,設置較短的失效時間;

分析:我們對null進行緩存,Redis需要更大的內存空間;此方案適用於請求key變化不頻繁的情況;如何黑客惡意攻擊,每次構建的不同的請求key,這種方案並不能從根本上解決此問題。
(2) 使用布隆過濾器,布隆過濾器優勢在於檢索一個元素是否在一個集合內;我們可以利用布隆過濾器來判斷請求的key是否在合理的范圍內,如果不存在,則直接過濾掉。

分析:可以利用Redis的 BitMap來實現布隆過濾器,用其來緩存目標數據集變化不頻繁,而請求key變化頻繁的情況。
Redis緩存雪崩
什么是緩存雪崩
緩存雪崩是指由於緩存集中過期或者緩存不可用,導致大量請求直接導向數據庫。在高並發的情況下,巨大的請求量有可能導致數據庫的崩潰,甚至導致整個應用體系的全盤崩潰;緩存失去保護是數據庫的作用,而巨大導致流量流向數據庫就是緩存雪崩的表現形式。
如何預防及避免
- 設置合理的過期策略,避免緩存集中過期。
- hotkeys分片存儲,避免請求數據的傾斜,導致緩存。
- hotkeys設置合理的過期時間或者“永不過期”。
Redis阻塞
我們知道Redis是單線程模型,如果線上Redis發生阻塞對整個應用將是毀滅性的;那什么原因會導致Redis阻塞呢?
API或數據結構使用不合理
常見的是在生產上執行時間復雜度高的命令如: KEYS,可以通過RENAME 方式將命令修改為不易猜測的,避免開發運維人員的不當執行。
數據結構的不合理,如存在頻繁操作bigkeys,有可能造成阻塞,將bigkeys拆分成成員較小的key。
CPU飽和
Redis單實例OPS可以到達平均10W+左右,如果你的Redis實例OPS已經達到較高的數值,那你可以考慮集群的水平擴展,來降低實例的OPS;但是你的Redis實例OPS不高,CPU使用率較高,那你應該檢查應用是否使用了時間復雜度較高的命令。
持久化阻塞
我們知道Redis可以進行持久化來防止數據的丟失;RDB方式,主進程會fork一個共享內存子進程來創建RDB文件,如果fork耗時過長,必然將阻塞主進程。
AOF刷盤,通常我們設置的刷盤策略是everysec,但由於磁盤壓力過大,fsync有可能耗時較長,當時間大於1秒時,為了保證數據安全,下次fsync調用將阻塞知道上次調用結束。
其他原因
CPU競爭:Redis是CPU密集型應用,應避免跟其他CPU密集型應用部署在一起
內存交換:Redis由於從內存中直接讀取,所以響應速度很快;當內存嚴重不足時,可能會存在內存交換,這將影響Redis的執行效率;通過cat /proc/$pid/smaps | grep Swap 來確認是否有頻繁的內存交換。 網絡問題:連接數限制或者網絡延時等也有可能導致阻塞
Redis淘汰策略
當Redis的內存使用達到限制時(可通過maxmemory <bytes>設置),會根據根據淘汰策略來移除Keys;有如下淘汰策略:
- allkeys-random:在所有keys中隨機移除
- allkeys-lru:在所有keys中使用lru移除
- allkeys-lfu:在所有keys中使用lfu移除
- volatile-random:在過期keys中隨機移除
- volatile-lru:在過期keys中使用lru移除
- volatile-lfu:在過期keys中使用lfu移除
- volatile-ttl:移除即將過期
- noevction:不移除任何key,空間不足時將拋出error
lru:Least Recently Used 最近最少使用
lfu:Least Frequently Used 最不經常使用
總結
本文介紹了bigkeys,hotkeys,緩存穿透,緩存雪崩,阻塞等問題;然而我們在實際應用中難免會遇到各種各樣的問題,本文難以一一列舉;但是面對問題我們要沉着冷靜,了解清楚問題的現象與本質,找到問題的症結所在,隨后在對症下葯便可以解決問題。