你的Redis為什麼變慢了?常見延遲問題定位與分析
Redis作為記憶體資料庫,擁有非常高的效能,單個例項的QPS能夠達到10W左右。但我們在使用Redis時,經常時不時會出現訪問延遲很大的情況,如果你不知道Redis的內部實現原理,在排查問題時就會一頭霧水。
很多時候,Redis出現訪問延遲變大,都與我們的使用不當或運維不合理導致的。
這篇文章我們就來分析一下Redis在使用過程中,經常會遇到的延遲問題以及如何定位和分析。
使用複雜度高的命令
如果在使用Redis時,發現訪問延遲突然增大,如何進行排查?
首先,第一步,建議你去檢視一下Redis的慢日誌。Redis提供了慢日誌命令的統計功能,我們通過以下設定,就可以檢視有哪些命令在執行時延遲比較大。
首先設定Redis的慢日誌閾值,只有超過閾值的命令才會被記錄,這裡的單位是微妙,例如設定慢日誌的閾值為5毫秒,同時設定只保留最近1000條慢日誌記錄:
1 2 3 4 | # 命令執行超過5毫秒記錄慢日誌 CONFIG SET slowlog-log-slower-than 5000 # 只保留最近1000條慢日誌 CONFIG SET slowlog-max-len 1000 |
設定完成之後,所有執行的命令如果延遲大於5毫秒,都會被Redis記錄下來,我們執行SLOWLOG get 5
查詢最近5條慢日誌:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 127.0.0.1:6379> SLOWLOG get 5 1) 1) (integer) 32693 # 慢日誌ID 2) (integer) 1593763337 # 執行時間 3) (integer) 5299 # 執行耗時(微妙) 4) 1) "LRANGE" # 具體執行的命令和引數 2) "user_list_2000" 3) "0" 4) "-1" 2) 1) (integer) 32692 2) (integer) 1593763337 3) (integer) 5044 4) 1) "GET" 2) "book_price_1000" ... |
通過檢視慢日誌記錄,我們就可以知道在什麼時間執行哪些命令比較耗時,如果你的業務經常使用O(N)
以上覆雜度的命令,例如sort
、sunion
、zunionstore
,或者在執行O(N)
命令時操作的資料量比較大,這些情況下Redis處理資料時就會很耗時。
如果你的服務請求量並不大,但Redis例項的CPU使用率很高,很有可能是使用了複雜度高的命令導致的。
解決方案就是,不使用這些複雜度較高的命令,並且一次不要獲取太多的資料,每次儘量操作少量的資料,讓Redis可以及時處理返回。
儲存bigkey
如果查詢慢日誌發現,並不是複雜度較高的命令導致的,例如都是SET
、DELETE
操作出現在慢日誌記錄中,那麼你就要懷疑是否存在Redis寫入了bigkey的情況。
Redis在寫入資料時,需要為新的資料分配記憶體,當從Redis中刪除資料時,它會釋放對應的記憶體空間。
如果一個key寫入的資料非常大,Redis在分配記憶體時也會比較耗時。同樣的,當刪除這個key的資料時,釋放記憶體也會耗時比較久。
你需要檢查你的業務程式碼,是否存在寫入bigkey的情況,需要評估寫入資料量的大小,業務層應該避免一個key存入過大的資料量。
那麼有沒有什麼辦法可以掃描現在Redis中是否存在bigkey的資料嗎?
Redis也提供了掃描bigkey的方法:
1 | redis-cli -h $host -p $port --bigkeys -i 0.01 |
使用上面的命令就可以掃描出整個例項key大小的分佈情況,它是以型別維度來展示的。
需要注意的是當我們線上上例項進行bigkey掃描時,Redis的QPS會突增,為了降低掃描過程中對Redis的影響,我們需要控制掃描的頻率,使用-i
引數控制即可,它表示掃描過程中每次掃描的時間間隔,單位是秒。
使用這個命令的原理,其實就是Redis在內部執行scan
命令,遍歷所有key,然後針對不同型別的key執行strlen
、llen
、hlen
、scard
、zcard
來獲取字串的長度以及容器型別(list/dict/set/zset)的元素個數。
而對於容器型別的key,只能掃描出元素最多的key,但元素最多的key不一定佔用記憶體最多,這一點需要我們注意下。不過使用這個命令一般我們是可以對整個例項中key的分佈情況有比較清晰的瞭解。
針對bigkey的問題,Redis官方在4.0版本推出了lazy-free
的機制,用於非同步釋放bigkey的記憶體,降低對Redis效能的影響。即使這樣,我們也不建議使用bigkey,bigkey在叢集的遷移過程中,也會影響到遷移的效能,這個後面在介紹叢集相關的文章時,會再詳細介紹到。
集中過期
有時你會發現,平時在使用Redis時沒有延時比較大的情況,但在某個時間點突然出現一波延時,而且報慢的時間點很有規律,例如某個整點,或者間隔多久就會發生一次。
如果出現這種情況,就需要考慮是否存在大量key集中過期的情況。
如果有大量的key在某個固定時間點集中過期,在這個時間點訪問Redis時,就有可能導致延遲增加。
Redis的過期策略採用主動過期+懶惰過期兩種策略:
- 主動過期:Redis內部維護一個定時任務,預設每隔100毫秒會從過期字典中隨機取出20個key,刪除過期的key,如果過期key的比例超過了25%,則繼續獲取20個key,刪除過期的key,迴圈往復,直到過期key的比例下降到25%或者這次任務的執行耗時超過了25毫秒,才會退出迴圈
- 懶惰過期:只有當訪問某個key時,才判斷這個key是否已過期,如果已經過期,則從例項中刪除
注意,Redis的主動過期的定時任務,也是在Redis主執行緒中執行的,也就是說如果在執行主動過期的過程中,出現了需要大量刪除過期key的情況,那麼在業務訪問時,必須等這個過期任務執行結束,才可以處理業務請求。此時就會出現,業務訪問延時增大的問題,最大延遲為25毫秒。
而且這個訪問延遲的情況,不會記錄在慢日誌裡。慢日誌中只記錄真正執行某個命令的耗時,Redis主動過期策略執行在操作命令之前,如果操作命令耗時達不到慢日誌閾值,它是不會計算在慢日誌統計中的,但我們的業務卻感到了延遲增大。
此時你需要檢查你的業務,是否真的存在集中過期的程式碼,一般集中過期使用的命令是expireat
或pexpireat
命令,在程式碼中搜尋這個關鍵字就可以了。
如果你的業務確實需要集中過期掉某些key,又不想導致Redis發生抖動,有什麼優化方案?
解決方案是,在集中過期時增加一個隨機時間,把這些需要過期的key的時間打散即可。
虛擬碼可以這麼寫:
1 2 | # 在過期時間點之後的5分鐘內隨機過期掉 redis.expireat(key, expire_time + random(300)) |
這樣Redis在處理過期時,不會因為集中刪除key導致壓力過大,阻塞主執行緒。
另外,除了業務使用需要注意此問題之外,還可以通過運維手段來及時發現這種情況。
做法是我們需要把Redis的各項執行資料監控起來,執行info
可以拿到所有的執行資料,在這裡我們需要重點關注expired_keys
這一項,它代表整個例項到目前為止,累計刪除過期key的數量。
我們需要對這個指標監控,當在很短時間內這個指標出現突增時,需要及時報警出來,然後與業務報慢的時間點對比分析,確認時間是否一致,如果一致,則可以認為確實是因為這個原因導致的延遲增大。
例項記憶體達到上限
有時我們把Redis當做純快取使用,就會給例項設定一個記憶體上限maxmemory
,然後開啟LRU淘汰策略。
當例項的記憶體達到了maxmemory
後,你會發現之後的每次寫入新的資料,有可能變慢了。
導致變慢的原因是,當Redis記憶體達到maxmemory
後,每次寫入新的資料之前,必須先踢出一部分資料,讓記憶體維持在maxmemory
之下。
這個踢出舊資料的邏輯也是需要消耗時間的,而具體耗時的長短,要取決於配置的淘汰策略:
- allkeys-lru:不管key是否設定了過期,淘汰最近最少訪問的key
- volatile-lru:只淘汰最近最少訪問並設定過期的key
- allkeys-random:不管key是否設定了過期,隨機淘汰
- volatile-random:只隨機淘汰有設定過期的key
- allkeys-ttl:不管key是否設定了過期,淘汰即將過期的key
- noeviction:不淘汰任何key,滿容後再寫入直接報錯
- allkeys-lfu:不管key是否設定了過期,淘汰訪問頻率最低的key(4.0+支援)
- volatile-lfu:只淘汰訪問頻率最低的過期key(4.0+支援)
具體使用哪種策略,需要根據業務場景來決定。
我們最常使用的一般是allkeys-lru
或volatile-lru
策略,它們的處理邏輯是,每次從例項中隨機取出一批key(可配置),然後淘汰一個最少訪問的key,之後把剩下的key暫存到一個池子中,繼續隨機取出一批key,並與之前池子中的key比較,再淘汰一個最少訪問的key。以此迴圈,直到記憶體降到maxmemory
之下。
如果使用的是allkeys-random
或volatile-random
策略,那麼就會快很多,因為是隨機淘汰,那麼就少了比較key訪問頻率時間的消耗了,隨機拿出一批key後直接淘汰即可,因此這個策略要比上面的LRU策略執行快一些。
但以上這些邏輯都是在訪問Redis時,真正命令執行之前執行的,也就是它會影響我們訪問Redis時執行的命令。
另外,如果此時Redis例項中有儲存bigkey,那麼在淘汰bigkey釋放記憶體時,這個耗時會更加久,延遲更大,這需要我們格外注意。
如果你的業務訪問量非常大,並且必須設定maxmemory
限制例項的記憶體上限,同時面臨淘汰key導致延遲增大的的情況,要想緩解這種情況,除了上面說的避免儲存bigkey、使用隨機淘汰策略之外,也可以考慮拆分例項的方法來緩解,拆分例項可以把一個例項淘汰key的壓力分攤到多個例項上,可以在一定程度降低延遲。
fork耗時嚴重
如果你的Redis開啟了自動生成RDB和AOF重寫功能,那麼有可能在後臺生成RDB和AOF重寫時導致Redis的訪問延遲增大,而等這些任務執行完畢後,延遲情況消失。
遇到這種情況,一般就是執行生成RDB和AOF重寫任務導致的。
生成RDB和AOF都需要父程式fork
出一個子程式進行資料的持久化,在fork
執行過程中,父程式需要拷貝記憶體頁表給子程式,如果整個例項記憶體佔用很大,那麼需要拷貝的記憶體頁表會比較耗時,此過程會消耗大量的CPU資源,在完成fork
之前,整個例項會被阻塞住,無法處理任何請求,如果此時CPU資源緊張,那麼fork
的時間會更長,甚至達到秒級。這會嚴重影響Redis的效能。
具體原理也可以參考我之前寫的文章:Redis持久化是如何做的?RDB和AOF對比分析。
我們可以執行info
命令,檢視最後一次fork
執行的耗時latest_fork_usec
,單位微妙。這個時間就是整個例項阻塞無法處理請求的時間。
除了因為備份的原因生成RDB之外,在主從節點第一次建立資料同步時,主節點也會生成RDB檔案給從節點進行一次全量同步,這時也會對Redis產生效能影響。
要想避免這種情況,我們需要規劃好資料備份的週期,建議在從節點上執行備份,而且最好放在低峰期執行。如果對於丟失資料不敏感的業務,那麼不建議開啟AOF和AOF重寫功能。
另外,fork
的耗時也與系統有關,如果把Redis部署在虛擬機器上,那麼這個時間也會增大。所以使用Redis時建議部署在物理機上,降低fork
的影響。
繫結CPU
很多時候,我們在部署服務時,為了提高效能,降低程式在使用多個CPU時上下文切換的效能損耗,一般會採用程式繫結CPU的操作。
但在使用Redis時,我們不建議這麼幹,原因如下。
繫結CPU的Redis,在進行資料持久化時,fork
出的子程式,子程式會繼承父程式的CPU使用偏好,而此時子程式會消耗大量的CPU資源進行資料持久化,子程式會與主程式發生CPU爭搶,這也會導致主程式的CPU資源不足訪問延遲增大。
所以在部署Redis程式時,如果需要開啟RDB和AOF重寫機制,一定不能進行CPU繫結操作!
AOF配合不合理
上面提到了,當執行AOF檔案重寫時會因為fork
執行耗時導致Redis延遲增大,除了這個之外,如果開啟AOF機制,設定的策略不合理,也會導致效能問題。
開啟AOF後,Redis會把寫入的命令實時寫入到檔案中,但寫入檔案的過程是先寫入記憶體,等記憶體中的資料超過一定閾值或達到一定時間後,記憶體中的內容才會被真正寫入到磁碟中。
AOF為了保證檔案寫入磁碟的安全性,提供了3種刷盤機制:
appendfsync always
:每次寫入都刷盤,對效能影響最大,佔用磁碟IO比較高,資料安全性最高appendfsync everysec
:1秒刷一次盤,對效能影響相對較小,節點當機時最多丟失1秒的資料appendfsync no
:按照作業系統的機制刷盤,對效能影響最小,資料安全性低,節點當機丟失資料取決於作業系統刷盤機制
當使用第一種機制appendfsync always
時,Redis每處理一次寫命令,都會把這個命令寫入磁碟,而且這個操作是在主執行緒中執行的。
記憶體中的的資料寫入磁碟,這個會加重磁碟的IO負擔,操作磁碟成本要比操作記憶體的代價大得多。如果寫入量很大,那麼每次更新都會寫入磁碟,此時機器的磁碟IO就會非常高,拖慢Redis的效能,因此我們不建議使用這種機制。
與第一種機制對比,appendfsync everysec
會每隔1秒刷盤,而appendfsync no
取決於作業系統的刷盤時間,安全性不高。因此我們推薦使用appendfsync everysec
這種方式,在最壞的情況下,只會丟失1秒的資料,但它能保持較好的訪問效能。
當然,對於有些業務場景,對丟失資料並不敏感,也可以不開啟AOF。
使用Swap
如果你發現Redis突然變得非常慢,每次訪問的耗時都達到了幾百毫秒甚至秒級,那此時就檢查Redis是否使用到了Swap,這種情況下Redis基本上已經無法提供高效能的服務。
我們知道,作業系統提供了Swap機制,目的是為了當記憶體不足時,可以把一部分記憶體中的資料換到磁碟上,以達到對記憶體使用的緩衝。
但當記憶體中的資料被換到磁碟上後,訪問這些資料就需要從磁碟中讀取,這個速度要比記憶體慢太多!
尤其是針對Redis這種高效能的記憶體資料庫來說,如果Redis中的記憶體被換到磁碟上,對於Redis這種效能極其敏感的資料庫,這個操作時間是無法接受的。
我們需要檢查機器的記憶體使用情況,確認是否確實是因為記憶體不足導致使用到了Swap。
如果確實使用到了Swap,要及時整理記憶體空間,釋放出足夠的記憶體供Redis使用,然後釋放Redis的Swap,讓Redis重新使用記憶體。
釋放Redis的Swap過程通常要重啟例項,為了避免重啟例項對業務的影響,一般先進行主從切換,然後釋放舊主節點的Swap,重新啟動服務,待資料同步完成後,再切換回主節點即可。
可見,當Redis使用到Swap後,此時的Redis的高效能基本被廢掉,所以我們需要提前預防這種情況。
我們需要對Redis機器的記憶體和Swap使用情況進行監控,在記憶體不足和使用到Swap時及時報警出來,及時進行相應的處理。
網路卡負載過高
如果以上產生效能問題的場景,你都規避掉了,而且Redis也穩定執行了很長時間,但在某個時間點之後開始,訪問Redis開始變慢了,而且一直持續到現在,這種情況是什麼原因導致的?
之前我們就遇到這種問題,特點就是從某個時間點之後就開始變慢,並且一直持續。這時你需要檢查一下機器的網路卡流量,是否存在網路卡流量被跑滿的情況。
網路卡負載過高,在網路層和TCP層就會出現資料傳送延遲、資料丟包等情況。Redis的高效能除了記憶體之外,就在於網路IO,請求量突增會導致網路卡負載變高。
如果出現這種情況,你需要排查這個機器上的哪個Redis例項的流量過大佔滿了網路頻寬,然後確認流量突增是否屬於業務正常情況,如果屬於那就需要及時擴容或遷移例項,避免這個機器的其他例項受到影響。
運維層面,我們需要對機器的各項指標增加監控,包括網路流量,在達到閾值時提前報警,及時與業務確認並擴容。
總結
以上我們總結了Redis中常見的可能導致延遲增大甚至阻塞的場景,這其中既涉及到了業務的使用問題,也涉及到Redis的運維問題。
可見,要想保證Redis高效能的執行,其中涉及到CPU、記憶體、網路,甚至磁碟的方方面面,其中還包括作業系統的相關特性的使用。
作為開發人員,我們需要了解Redis的執行機制,例如各個命令的執行時間複雜度、資料過期策略、資料淘汰策略等,使用合理的命令,並結合業務場景進行優化。
作為DBA運維人員,需要了解資料持久化、作業系統fork
原理、Swap機制等,並對Redis的容量進行合理規劃,預留足夠的機器資源,對機器做好完善的監控,才能保證Redis的穩定執行。
相關文章
- redis為什麼變慢了?這些原因你都知道嗎Redis
- 分析伺服器延遲的問題伺服器
- 為什麼延遲刪除可以保證MYSQL 與redis的一致性?MySqlRedis
- Redis面試常見問題Redis面試
- 一次 RocketMQ 順序消費延遲的問題定位MQ
- Redis資料操作長延遲分析Redis
- 什麼是高延遲檔案傳輸?為什麼要使用高延遲檔案傳輸
- Redis常見問題彙總Redis
- 美國伺服器延遲高怎麼辦,如何解決延遲問題伺服器
- springboot為什麼要用延遲匯入?Spring Boot
- 當 Redis 發生高延遲時,到底發生了什麼Redis
- sonar常見問題分析
- 幾種常見的延遲執行處理方式
- Redis Manager 常見問題彙總Redis
- redis 叢集常見問題 QARedis
- Redis常見問題(快取雪崩)Redis快取
- 為什麼想來我們公司工作?- 面試常見問題解析面試
- 為什麼網站使用CDN加速後,網站訪問速度反而變慢了?網站
- redis 延遲佇列Redis佇列
- 【MySQL】常見slave 延遲原因以及解決方法MySql
- Google 怎麼解決長尾延遲問題Go
- 怎麼解決伺服器延遲問題伺服器
- Redis 常見的效能問題和解決方法Redis
- 為什麼使用Redis及其產品定位Redis
- 如何分析Sonar常見問題?
- 為什麼延遲是儲存中最重要的指標指標
- 巧用閃回查詢來分析事務延遲的問題
- 定位 UNIX 上常見問題的經驗總結
- 漫畫 | Redis常見面試問題(一)Redis面試
- Redis 快取常見問題處理Redis快取
- MyCAT的常見問題分析和解決
- 你知道Redis可以實現延遲佇列嗎?Redis佇列
- 【MySQL】六、常見slave 延遲原因以及解決方法MySql
- 手把手教你定位常見Java效能問題Java
- 10個常見的Redis面試"刁難"問題Redis面試
- Redis常見的效能問題和解決方法UWRedis
- 關於延遲塊清除的原理是什麼?為什麼要進行塊清除
- Redis常見面試題Redis面試題