首頁  >  文章  >  資料庫  >  淺談Redis變慢的原因及檢驗方法

淺談Redis變慢的原因及檢驗方法

WBOY
WBOY轉載
2022-09-09 13:51:372303瀏覽

推薦學習:Redis影片教學

#原因1:實例記憶體達到上限

排查思路

如果你的Redis 實例設定了記憶體上限maxmemory,那麼也有可能導致Redis 變慢。

當我們把 Redis 當做純快取使用時,通常會為這個實例設定一個記憶體上限 maxmemory,然後設定一個資料淘汰策略。而當實例的記憶體達到了 maxmemory 後,你可能會發現,在此之後每次寫入新數據,操作延遲變大了。

導致變慢的原因

當Redis 記憶體達到maxmemory 後,每次寫入新的資料之前,Redis 必須先從實例中踢出一部分數據,讓整個實例的記憶體維持在maxmemory 之下,然後才能把新資料寫進來。

這個踢出舊資料的邏輯也是需要消耗時間的,而具體耗時的長短,要取決於你配置的淘汰策略:

  • allkeys-lru:不管key是否設定了過期,淘汰最近最少訪問的key
  • volatile-lru:只淘汰最近最少訪問、並設定了過期時間的key
  • allkeys-random:不管key 是否設定了過期,隨機淘汰key
  • volatile-random:只隨機淘汰設定了過期時間的key
  • allkeys-ttl:不管key 是否設定了過期,淘汰即將過期的key
  • noeviction:不淘汰任何key,實例記憶體達到maxmeory 後,再寫入新資料直接回傳錯誤
  • allkeys-lfu:不管key 是否設定了過期,淘汰存取頻率最低的key(4.0 版本支援)
  • volatile-lfu:只淘汰訪問頻率最低、並設定了過期時間key(4.0 版本支援)

        具體使用哪種策略,我們需要根據特定的業務場景來配置。一般最常使用的是allkeys-lru / volatile-lru 淘汰策略,它們的處理邏輯是,每次從實例中隨機取出一批key(這個數量可配置),然後淘汰一個最少訪問的key,之後把剩下的key 暫存到一個池子中,繼續隨機取一批key,並與先前池子中的key 比較,再淘汰一個最少訪問的key。以此往復,直到實例記憶體降到 maxmemory 之下。

        需要注意的是,Redis 的淘汰資料的邏輯與刪除過期key 的一樣,也是在命令真正執行之前執行的,也就是說它也會增加我們操作Redis 的延遲,而且,寫OPS越高,延遲也會越明顯。

另外,如果此時你的 Redis 實例中還儲存了 bigkey,那麼在淘汰刪除 bigkey 釋放記憶體時,也會耗時比較久。

看到了麼? bigkey 的危害到處都是,這也是前面我提醒你盡量不儲存 bigkey 的原因。

解決方案

  • 避免儲存bigkey,降低釋放記憶體的耗時
  • 淘汰策略改為隨機淘汰,隨機淘汰比LRU要快很多(視業務狀況調整)
  • 分割實例,把淘汰key 的壓力分攤到多個實例上
  • 如果使用的是Redis 4.0 以上版本,開啟layz-free 機制,把淘汰key 釋放記憶體的操作放到後台執行緒執行(設定lazyfree-lazy-eviction = yes)

原因2:開啟記憶體大頁

#排查想法

  • 我們都知道,應用程式向作業系統申請記憶體時,是按記憶體頁申請的,而常規的記憶體頁大小是4KB。
  • Linux 核心從 2.6.38 開始,支援了記憶體大頁機制,該機制允許應用程式以 2MB 大小為單位,向作業系統申請記憶體。
  • 應用程式每次向作業系統申請的記憶體單位變大了,但這也意味著申請記憶體的耗時變長。

導致變慢的原因

#
  • 當 Redis 在執行後台 RDB 和 AOF rewrite 時,會採用 fork 子程序的方式來處理。但主進程 fork 子進程後,此時的主進程依舊是可以接收寫入請求的,而進來的寫入請求,會採用 Copy On Write(寫時複製)的方式操作記憶體資料。
  • 也就是說,主程序一旦有數據需要修改,Redis 並不會直接修改現有記憶體中的數據,而是先將這塊記憶體資料拷貝出來,再修改這塊新記憶體的數據,這就是所謂的「寫時複製」。
  • 寫時複製你也可以理解成,誰需要發生寫入操作,誰就需要先拷貝,再修改。
  • 這樣做的好處是,父進程有任何寫入操作,並不會影響子進程的資料持久化(子進程只持久化fork 這一瞬間整個實例中的所有資料即可,不關心新的資料變更,因為子進程只需要一份記憶體快照,然後持久化到磁碟上)。
  • 但是請注意,主進程在拷貝記憶體資料時,這個階段就涉及到新記憶體的申請,如果此時作業系統開啟了記憶體大頁,那麼在此期間,客戶端即便只修改10B 的數據,Redis 在申請記憶體時也會以2MB 為單位向作業系統申請,申請記憶體的耗時變長,進而導致每個寫入請求的延遲增加,影響到Redis 效能。
  • 同樣地,如果這個寫請求操作的是一個 bigkey,那麼主程序在拷貝這個 bigkey 記憶體區塊時,一次申請的記憶體會更大,時間也會更久。可見,bigkey 在這裡又一次影響到了效能。

解決方案

#關閉記憶體大頁機制。

首先,你需要查看Redis 機器是否開啟了記憶體大頁:

$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

如果輸出選項是always,就表示目前開啟了記憶體大頁機制,我們需要關掉它:

$ echo never > /sys/kernel/mm/transparent_hugepage/enabled

其實,作業系統提供的記憶體大頁機制,其優點是,可以在一定程式上降低應用程式申請記憶體的次數。

但是對於 Redis 這種對效能和延遲極其敏感的資料庫來說,我們希望 Redis 在每次申請記憶體時,耗時盡量短,所以我不建議你在 Redis 機器上開啟這個機制。

原因3:使用Swap

檢查思路

#如果你發現Redis 突然變得非常慢,每次的操作耗時都達到了幾百毫秒甚至秒級,那此時你就需要檢查Redis 是否使用到了Swap,在這種情況下Redis 基本上已經無法提供高效能的服務了。

導致變慢的原因

什麼是 Swap?為什麼使用 Swap 會導致 Redis 的效能下降?

如果你對作業系統有些了解,就會知道作業系統為了緩解記憶體不足對應用程式的影響,允許把一部分記憶體中的資料換到磁碟上,以達到應用程式對記憶體使用的緩衝,這些記憶體資料換到磁碟上的區域,就是Swap。

問題就在於,當記憶體中的資料換到磁碟上後,Redis 再存取這些資料時,就需要從磁碟上讀取,存取磁碟的速度要比存取記憶體慢幾百倍!尤其是針對 Redis 這種對效能要求極高、效能極為敏感的資料庫來說,這個操作延遲是無法接受的。

此時,你需要檢查 Redis 機器的記憶體使用情況,確認是否存在使用了 Swap。你可以透過以下方式來查看 Redis 進程是否使用到了 Swap:

# 先找到 Redis 的进程 ID
$ ps -aux | grep redis-server
 
# 查看 Redis Swap 使用情况
$ cat /proc/$pid/smaps | egrep '^(Swap|Size)'

輸出結果如下

Size:               1256 kB
Swap:                 4 kB
Swap:                                0 kB
Size: 63488 kB
Swap:                  0 kB
Size:                0 kB
Size:              65404 kB##          1921024 kB
Swap:                  0 kB
...


這個結果會列出Redis 行程的記憶體使用量。

每一行Size 表示Redis 所用的一塊內存大小,Size 下面的Swap 就表示這塊Size 大小的內存,有多少資料已經被換到磁碟上了,如果這兩個值相等,說明這區塊記憶體的資料都已經完全被換到磁碟上了。

如果只是少量資料被換到磁碟上,例如每一塊 Swap 佔對應 Size 的比例很小,那麼影響並不是很大。如果是幾百兆甚至上 GB 的記憶體被換到了磁碟上,那麼你就需要警惕了,這種情況 Redis 的效能肯定會急劇下降。

解決方案

增加機器的內存,讓Redis 有足夠的記憶體可以使用

整理記憶體空間,釋放出足夠的記憶體供Redis 使用,然後釋放Redis 的Swap,讓Redis 重新使用記憶體

    #釋放Redis 的Swap 過程通常要重啟實例,為了避免重啟實例對業務的影響,一般會先進行主從切換,然後釋放舊主節點的Swap,重啟舊主節點實例,待從庫資料同步完成後,再進行主從切換即可。
  • 可見,當 Redis 使用到 Swap 後,此時的 Redis 性能基本上已達不到高性能的要求(你可以理解為武功被廢),所以你也需要提前預防這種情況。
  • 預防的辦法就是,你需要對 Redis 機器的記憶體和 Swap 使用情況進行監控,在記憶體不足或使用到 Swap 時報警出來,及時處理。

原因4:網路頻寬過載 

排查想法

如果以上產生效能問題的場景,你都規避掉了,而且Redis 也穩定運行了很長一段時間,但在某個時間點之後開始,操作Redis 突然開始變慢了,而且一直持續下去,這種情況又是什麼原因導致?

此時你需要排查 Redis 機器的網路頻寬是否過載,是否存在某個實例把整台機器的網路頻寬佔滿的狀況。

導致變慢的原因

網路頻寬過載的情況下,伺服器在 TCP 層和網路層就會出現封包發送延遲、丟包等情況。

Redis 的高效能,除了操作記憶體之外,就在於網路 IO 了,如果網路 IO 存在瓶頸,那麼也會嚴重影響 Redis 的效能。

解決方案

及時確認佔滿網路頻寬Redis 實例,如果屬於正常的業務訪問,那就需要及時擴容或遷移實例了,避免因為這個實例流量太大,影響這個機器的其他實例。

維運層面,你需要對 Redis 機器的各項指標增加監控,包括網路流量,在網路流量達到一定閾值時提前報警,及時確認和擴容。

    原因5:其他原因
  • 1) 頻繁短連線

你的業務應用,應該使用長連線操作Redis,避免頻繁的短連接。

頻繁的短連接會導致 Redis 大量時間耗費在連接的建立和釋放上,TCP 的三次握手和四次揮手同樣也會增加訪問延遲。

2) 運維監控

前面我也提到了,要提前預知Redis 變慢的情況發生,不可或缺的就是做好完善的監控。

監控其實就是對採集 Redis 的各項運行時指標,通常的做法是監控程序定時採集 Redis 的 INFO 信息,然後根據 INFO 信息中的狀態數據做數據展示和警報。

這裡我需要提醒你的是,在寫一些監控腳本,或使用開源的監控元件時,也不能掉以輕心。

在寫入監控腳本存取 Redis 時,盡量採用長連接的方式採集狀態訊息,避免頻繁短連接。同時,你也要注意控制存取 Redis 的頻率,避免影響到業務請求。

在使用一些開源的監控元件時,最好了解一下這些元件的實作原理,以及正確配置這些元件,防止出現監控元件發生Bug,導致短時大量操作Redis,影響Redis 效能的情況發生。

我們當時就發生過,DBA 在使用一些開源元件時,因為配置和使用問題,導致監控程式頻繁地與 Redis 建立和斷開連接,導致 Redis 回應變慢。

3)其它程式爭搶資源

最後需要提醒你的是,你的Redis 機器最好專項專用,只用來部署Redis 實例,不要部署其他應用程序,盡量給Redis 一個相對「安靜」的環境,避免其它程式佔用CPU、記憶體、磁碟資源,導致分配給Redis 的資源不足而受到影響。

推薦學習:Redis影片教學

以上是淺談Redis變慢的原因及檢驗方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:jb51.net。如有侵權,請聯絡admin@php.cn刪除