Redis 突然變慢了如何排查并解決?

      網友投稿 656 2025-03-31

      Redis 通常是我們業務系統中一個重要的組件,比如:緩存、賬號登錄信息、排行榜等。

      一旦 Redis 請求延遲增加,可能就會導致業務系統“雪崩”。

      我在單身紅娘婚戀類型互聯網公司工作,在雙十一推出下單就送女朋友的活動。

      誰曾想,凌晨 12 點之后,用戶量暴增,出現了一個技術故障,用戶無法下單,當時老大火冒三丈!

      經過查找發現?Redis?報?Could not get a resource from the pool。

      獲取不到連接資源,并且集群中的單臺 Redis 連接量很高。

      大量的流量沒了 Redis 的緩存響應,直接打到了 MySQL,最后數據庫也宕機了……

      于是各種更改最大連接數、連接等待數,雖然報錯信息頻率有所緩解,但還是持續報錯。

      后來經過線下測試,發現存放?Redis?中的字符數據很大,平均 1s 返回數據。

      可以發現,一旦 Redis 延遲過高,會引發各種問題。

      今天跟大家一起來分析下如何確定 Redis 有性能問題和解決方案。

      目錄

      延遲基線測量

      慢指令監控

      慢日志功能

      Latency Monitoring

      網絡通信導致的延遲

      慢指令導致的延遲

      Fork 生成 RDB 導致的延遲

      內存大頁(transparent huge pages)

      swap:操作系統分頁

      獲取 Redis 實例 pid

      解決方案

      AOF 和磁盤 I/O 導致的延遲

      expires 淘汰過期數據

      解決方案

      bigkey

      查找 bigkey

      解決方案

      Redis 性能出問題了么?

      最大延遲是客戶端發出命令到客戶端收到命令的響應的時間,正常情況下 Redis 處理的時間極短,在微秒級別。

      當 Redis 出現性能波動的時候,比如達到幾秒到十幾秒,這個很明顯我們可以認定 Redis 性能變慢了。

      有的硬件配置比較高,當延遲 0.6ms,我們可能就認定變慢了。硬件比較差的可能 3 ms 我們才認為出現問題。

      那我們該如何定義 Redis 真的變慢了呢?

      所以,我們需要對當前環境的?Redis 基線性能做測量,也就是在一個系統在低壓力、無干擾情況下的基本性能。

      當你發現 Redis 運行時時的延遲是基線性能的 2 倍以上,就可以判定 Redis 性能變慢了。

      延遲基線測量

      redis-cli 命令提供了–intrinsic-latency?選項,用來監測和統計測試期間內的最大延遲(以毫秒為單位),這個延遲可以作為 Redis 的基線性能。

      redis-cli?--latency?-h?`host`?-p?`port`

      比如執行如下指令:

      redis-cli?--intrinsic-latency?100

      Max?latency?so?far:?4?microseconds.

      Max?latency?so?far:?18?microseconds.

      Max?latency?so?far:?41?microseconds.

      Max?latency?so?far:?57?microseconds.

      Max?latency?so?far:?78?microseconds.

      Max?latency?so?far:?170?microseconds.

      Max?latency?so?far:?342?microseconds.

      Max?latency?so?far:?3079?microseconds.

      45026981?total?runs?(avg?latency:?2.2209?microseconds?/?2220.89?nanoseconds?per?run).

      Worst?run?took?1386x?longer?than?the?average?latency.

      注意:參數100是測試將執行的秒數。我們運行測試的時間越長,我們就越有可能發現延遲峰值。

      通常運行 100 秒通常是合適的,足以發現延遲問題了,當然我們可以選擇不同時間運行幾次,避免誤差。

      運行的最大延遲是 3079 微秒,所以基線性能是 3079 (3 毫秒)微秒。

      需要注意的是,我們要在 Redis 的服務端運行,而不是客戶端。這樣,可以避免網絡對基線性能的影響。

      可以通過?-h host -p port?來連接服務端,如果想監測網絡對 Redis 的性能影響,可以使用 Iperf 測量客戶端到服務端的網絡延遲。

      如果網絡延遲幾百毫秒,說明網絡可能有其他大流量的程序在運行導致網絡擁塞,需要找運維協調網絡的流量分配。

      慢指令監控

      如何判斷是否是慢指令呢?

      看操作復雜度是否是O(N)。官方文檔對每個命令的復雜度都有介紹,盡可能使用O(1) 和 O(log N)命令。

      涉及到集合操作的復雜度一般為O(N),比如集合全量查詢HGETALL、SMEMBERS,以及集合的聚合操作:SORT、LREM、 SUNION等。

      有監控數據可以觀測呢?代碼不是我寫的,不知道有沒有人用了慢指令。

      有兩種方式可以排查到:

      使用 Redis 慢日志功能查出慢命令;

      latency-monitor(延遲監控)工具。

      此外,可以使用自己(top、htop、prstat 等)快速檢查 Redis 主進程的 CPU 消耗。如果 CPU 使用率很高而流量不高,通常表明使用了慢速命令。

      慢日志功能

      Redis 中的 slowlog 命令可以讓我們快速定位到那些超出指定執行時間的慢命令,默認情況下命令若是執行時間超過 10ms 就會被記錄到日志。

      slowlog 只會記錄其命令執行的時間,不包含 io 往返操作,也不記錄單由網絡延遲引起的響應慢。

      我們可以根據基線性能來自定義慢命令的標準(配置成基線性能最大延遲的 2 倍),調整觸發記錄慢命令的閾值。

      可以在 redis-cli 中輸入以下命令配置記錄 6 毫秒以上的指令:

      redis-cli?CONFIG?SET?slowlog-log-slower-than?6000

      也可以在 Redis.config 配置文件中設置,以微秒為單位。

      想要查看所有執行時間比較慢的命令,可以通過使用 Redis-cli 工具,輸入 slowlog get 命令查看,返回結果的第三個字段以微秒位單位顯示命令的執行時間。

      假如只需要查看最后 2 個慢命令,輸入 slowlog get 2 即可。

      示例:獲取最近2個慢查詢命令

      127.0.0.1:6381>?SLOWLOG?get?2

      1)?1)?(integer)?6

      2)?(integer)?1458734263

      3)?(integer)?74372

      4)?1)?"hgetall"

      2)?"max.dsp.blacklist"

      2)?1)?(integer)?5

      2)?(integer)?1458734258

      3)?(integer)?5411075

      4)?1)?"keys"

      2)?"max.dsp.blacklist"

      以第一個 HGET 命令為例分析,每個 slowlog 實體共 4 個字段:

      字段 1:1 個整數,表示這個 slowlog 出現的序號,server 啟動后遞增,當前為 6。

      字段 2:表示查詢執行時的 Unix 時間戳。

      字段 3:表示查詢執行微秒數,當前是 74372 微秒,約 74ms。

      字段 4: 表示查詢的命令和參數,如果參數很多或很大,只會顯示部分參數個數。當前命令是hgetall max.dsp.blacklist。

      Latency Monitoring

      Redis 在 2.8.13 版本引入了 Latency Monitoring 功能,用于以秒為粒度監控各種事件的發生頻率。

      啟用延遲監視器的第一步是設置延遲閾值(單位毫秒)。只有超過該閾值的時間才會被記錄,比如我們根據基線性能(3ms)的 3 倍設置閾值為 9 ms。

      可以用 redis-cli 設置也可以在 Redis.config 中設置;

      CONFIG?SET?latency-monitor-threshold?9

      工具記錄的相關事件的詳情可查看官方文檔:https://redis.io/topics/latency-monitor

      如獲取最近的 latency

      127.0.0.1:6379>?debug?sleep?2

      OK

      (2.00s)

      127.0.0.1:6379>?latency?latest

      1)?1)?"command"

      2)?(integer)?1645330616

      3)?(integer)?2003

      4)?(integer)?2003

      事件的名稱;

      事件發生的最新延遲的 Unix 時間戳;

      毫秒為單位的時間延遲;

      該事件的最大延遲。

      如何解決 Redis 變慢?

      Redis 的數據讀寫由單線程執行,如果主線程執行的操作時間太長,就會導致主線程阻塞。

      一起分析下都有哪些操作會阻塞主線程,我們又該如何解決?

      網絡通信導致的延遲

      客戶端使用 TCP/IP 連接或 Unix 域連接連接到 Redis。1 Gbit/s 網絡的典型延遲約為 200 us。

      redis 客戶端執行一條命令分 4 個過程:

      發送命令-〉 命令排隊 -〉 命令執行-〉 返回結果

      這個過程稱為 Round trip time(簡稱 RTT, 往返時間),mget mset 有效節約了 RTT,但大部分命令(如 hgetall,并沒有 mhgetall)不支持批量操作,需要消耗 N 次 RTT ,這個時候需要 pipeline 來解決這個問題。

      Redis pipeline 將多個命令連接在一起來減少網絡響應往返次數。

      慢指令導致的延遲

      根據上文的慢指令監控查詢文檔,查詢到慢查詢指令。可以通過以下兩種方式解決:

      比如在 Cluster 集群中,將聚合運算等 O(N) 操作運行在 slave 上,或者在客戶端完成。

      使用高效的命令代替。使用增量迭代的方式,避免一次查詢大量數據,具體請查看SCAN、SSCAN、HSCAN和ZSCAN命令。

      除此之外,生產中禁用KEYS 命令,它只適用于調試。因為它會遍歷所有的鍵值對,所以操作延時高。

      Fork 生成 RDB 導致的延遲

      生成 RDB 快照,Redis 必須 fork 后臺進程。fork 操作(在主線程中運行)本身會導致延遲。

      Redis 使用操作系統的多進程寫時復制技術 COW(Copy On Write)?來實現快照持久化,減少內存占用。

      但 fork 會涉及到復制大量鏈接對象,一個 24 GB 的大型 Redis 實例需要 24 GB / 4 kB * 8 = 48 MB 的頁表。

      執行 bgsave 時,這將涉及分配和復制 48 MB 內存。

      此外,從庫加載 RDB 期間無法提供讀寫服務,所以主庫的數據量大小控制在 2~4G 左右,讓從庫快速的加載完成。

      內存大頁(transparent huge pages)

      常規的內存頁是按照 4 KB 來分配,Linux 內核從 2.6.38 開始支持內存大頁機制,該機制支持 2MB 大小的內存頁分配。

      Redis 使用了 fork 生成 RDB 做持久化提供了數據可靠性保證。

      當生成 RDB 快照的過程中,Redis 采用**寫時復制**技術使得主線程依然可以接收客戶端的寫請求。

      也就是當數據被修改的時候,Redis 會復制一份這個數據,再進行修改。

      采用了內存大頁,生成 RDB 期間,即使客戶端修改的數據只有 50B 的數據,Redis 需要復制 2MB 的大頁。當寫的指令比較多的時候就會導致大量的拷貝,導致性能變慢。

      使用以下指令禁用 Linux 內存大頁即可:

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

      swap:操作系統分頁

      當物理內存(內存條)不夠用的時候,將部分內存上的數據交換到 swap 空間上,以便讓系統不會因內存不夠用而導致 oom 或者更致命的情況出現。

      當某進程向 OS 請求內存發現不足時,OS 會把內存中暫時不用的數據交換出去,放在 SWAP 分區中,這個過程稱為 SWAP OUT。

      當某進程又需要這些數據且 OS 發現還有空閑物理內存時,又會把 SWAP 分區中的數據交換回物理內存中,這個過程稱為 SWAP IN。

      內存 swap 是操作系統里將內存數據在內存和磁盤間來回換入和換出的機制,涉及到磁盤的讀寫。

      觸發 swap 的情況有哪些呢?

      對于 Redis 而言,有兩種常見的情況:

      Redis 使用了比可用內存更多的內存;

      與 Redis 在同一機器運行的其他進程在執行大量的文件讀寫 I/O 操作(包括生成大文件的 RDB 文件和 AOF 后臺線程),文件讀寫占用內存,導致 Redis 獲得的內存減少,觸發了 swap。

      我要如何排查是否因為 swap 導致的性能變慢呢?

      Linux 提供了很好的工具來排查這個問題,所以當懷疑由于交換導致的延遲時,只需按照以下步驟排查。

      獲取 Redis 實例 pid

      $?redis-cli?info?|?grep?process_id

      process_id:13160

      進入此進程的 /proc 文件系統目錄:

      cd?/proc/13160

      在這里有一個 smaps 的文件,該文件描述了 Redis 進程的內存布局,運行以下指令,用 grep 查找所有文件中的 Swap 字段。

      $?cat?smaps?|?egrep?'^(Swap|Size)'

      Size:????????????????316?kB

      Swap:??????????????????0?kB

      Size:??????????????????4?kB

      Swap:??????????????????0?kB

      Size:??????????????????8?kB

      Swap:??????????????????0?kB

      Size:?????????????????40?kB

      Swap:??????????????????0?kB

      Size:????????????????132?kB

      Swap:??????????????????0?kB

      Size:?????????????720896?kB

      Swap:?????????????????12?kB

      每行 Size 表示 Redis 實例所用的一塊內存大小,和 Size 下方的 Swap 對應這塊 Size 大小的內存區域有多少數據已經被換出到磁盤上了。

      如果 Size == Swap 則說明數據被完全換出了。

      可以看到有一個 720896 kB 的內存大小有 12 kb 被換出到了磁盤上(僅交換了 12 kB),這就沒什么問題。

      Redis 本身會使用很多大小不一的內存塊,所以,你可以看到有很多 Size 行,有的很小,就是 4KB,而有的很大,例如 720896KB。不同內存塊被換出到磁盤上的大小也不一樣。

      敲重點了

      如果 Swap 一切都是 0 kb,或者零星的 4k ,那么一切正常。

      當出現百 MB,甚至 GB 級別的 swap 大小時,就表明,此時,Redis 實例的內存壓力很大,很有可能會變慢。

      解決方案

      增加機器內存;

      Redis 突然變慢了如何排查并解決?

      將 Redis 放在單獨的機器上運行,避免在同一機器上運行需要大量內存的進程,從而滿足 Redis 的內存需求;

      增加 Cluster 集群的數量分擔數據量,減少每個實例所需的內存。

      AOF 和磁盤 I/O 導致的延遲

      為了保證數據可靠性,Redis 使用 AOF 和 RDB 快照實現快速恢復和持久化。

      可以使用 appendfsync 配置將 AOF 配置為以三種不同的方式在磁盤上執行 write 或者 fsync (可以在運行時使用?CONFIG SET命令修改此設置,比如:redis-cli CONFIG SET appendfsync no)。

      no:Redis 不執行 fsync,唯一的延遲來自于 write 調用,write 只需要把日志記錄寫到內核緩沖區就可以返回。

      everysec:Redis 每秒執行一次 fsync。使用后臺子線程異步完成 fsync 操作。最多丟失 1s 的數據。

      always:每次寫入操作都會執行 fsync,然后用 OK 代碼回復客戶端(實際上 Redis 會嘗試將同時執行的許多命令聚集到單個 fsync 中),沒有數據丟失。在這種模式下,性能通常非常低,強烈建議使用快速磁盤和可以在短時間內執行 fsync 的文件系統實現。

      我們通常將 Redis 用于緩存,數據丟失完全惡意從數據獲取,并不需要很高的數據可靠性,建議設置成 no 或者 everysec。

      除此之外,避免 AOF 文件過大, Redis 會進行 AOF 重寫,生成縮小的 AOF 文件。

      可以把配置項?no-appendfsync-on-rewrite設置為 yes,表示在 AOF 重寫時,不進行 fsync 操作。

      也就是說,Redis 實例把寫命令寫到內存后,不調用后臺線程進行 fsync 操作,就直接返回了。

      expires 淘汰過期數據

      Redis 有兩種方式淘汰過期數據:

      惰性刪除:當接收請求的時候發現 key 已經過期,才執行刪除;

      定時刪除:每 100 毫秒刪除一些過期的 key。

      定時刪除的算法如下:

      隨機采樣 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP個數的 key,刪除所有過期的 key;

      如果發現還有超過 25% 的 key 已過期,則執行步驟一。

      ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP默認設置為 20,每秒執行 10 次,刪除 200 個 key 問題不大。

      如果觸發了第二條,就會導致 Redis 一致在刪除過期數據去釋放內存。而刪除是阻塞的。

      觸發條件是什么呀?

      也就是大量的 key 設置了相同的時間參數。同一秒內,大量 key 過期,需要重復刪除多次才能降低到 25% 以下。

      簡而言之:大量同時到期的 key 可能會導致性能波動。

      解決方案

      如果一批 key 的確是同時過期,可以在?EXPIREAT?和?EXPIRE?的過期時間參數上,加上一個一定大小范圍內的隨機數,這樣,既保證了 key 在一個鄰近時間范圍內被刪除,又避免了同時過期造成的壓力。

      bigkey

      通常我們會將含有較大數據或含有大量成員、列表數的 Key 稱之為大 Key,下面我們將用幾個實際的例子對大 Key 的特征進行描述:

      一個 STRING 類型的 Key,它的值為 5MB(數據過大)

      一個 LIST 類型的 Key,它的列表數量為 10000 個(列表數量過多)

      一個 ZSET 類型的 Key,它的成員數量為 10000 個(成員數量過多)

      一個 HASH 格式的 Key,它的成員數量雖然只有 1000 個但這些成員的 value 總大小為 10MB(成員體積過大)

      bigkey 帶來問題如下:

      Redis 內存不斷變大引發 OOM,或者達到 maxmemory 設 置值引發寫阻塞或重要 Key 被逐出;

      Redis Cluster 中的某個 node 內存遠超其余 node,但因 Redis Cluster 的數據遷移最小粒度為 Key 而無法將 node 上的內存均衡化;

      bigkey 的讀請求占用過大帶寬,自身變慢的同時影響到該服務器上的其它服務;

      刪除一個 bigkey 造成主庫較長時間的阻塞并引發同步中斷或主從切換;

      查找 bigkey

      使用 redis-rdb-tools 工具以定制化方式找出大 Key。

      解決方案

      如將一個含有數萬成員的 HASH Key 拆分為多個 HASH Key,并確保每個 Key 的成員數量在合理范圍,在 Redis Cluster 結構中,大 Key 的拆分對 node 間的內存平衡能夠起到顯著作用。

      Redis 自 4.0 起提供了 UNLINK 命令,該命令能夠以非阻塞的方式緩慢逐步的清理傳入的 Key,通過 UNLINK,你可以安全的刪除大 Key 甚至特大 Key。

      總結

      如下檢查清單,幫助你在遇到 Redis 性能變慢的時候能高效解決問題。

      獲取當前 Redis 的基線性能;

      開啟慢指令監控,定位慢指令導致的問題;

      找到慢指令,使用 scan 的方式;

      將實例的數據大小控制在 2-4GB,避免主從復制加載過大 RDB 文件而阻塞;

      禁用內存大頁,采用了內存大頁,生成 RDB 期間,即使客戶端修改的數據只有 50B 的數據,Redis 需要復制 2MB 的大頁。當寫的指令比較多的時候就會導致大量的拷貝,導致性能變慢。

      Redis 使用的內存是否過大導致 swap;

      AOF 配置是否合理,可以將配置項 no-appendfsync-on-rewrite 設置為 yes,避免 AOF 重寫和 fsync 競爭磁盤 IO 資源,導致 Redis 延遲增加。

      bigkey 會帶來一系列問題,我們需要進行拆分防止出現 bigkey,并通過 UNLINK 異步刪除。

      Redis 網絡

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:怎么制作弧形文字(怎么把文字做成弧形)
      下一篇:制造業安全生產管理臺賬(企業如何做好安全生產管理臺賬工作)
      相關文章
      国产亚洲A∨片在线观看| 国产a v无码专区亚洲av| 亚洲国产精品成人精品无码区| 国产亚洲精品AA片在线观看不加载| 在线亚洲v日韩v| 国产99久久亚洲综合精品| 精品韩国亚洲av无码不卡区| 精品久久亚洲一级α| 国产精品亚洲精品日韩动图| 亚洲AV中文无码乱人伦| 亚洲精品NV久久久久久久久久| 亚洲欧洲日本在线| 亚洲综合色区在线观看| 自拍偷自拍亚洲精品第1页| 亚洲人成亚洲人成在线观看 | 日日噜噜噜噜夜夜爽亚洲精品| 亚洲免费视频一区二区三区| 亚洲真人日本在线| 亚洲色成人网站WWW永久| 亚洲日韩乱码中文无码蜜桃臀网站 | 国产午夜亚洲精品不卡| 国产精品自拍亚洲| 亚洲一区二区三区无码影院| 国产亚洲精品无码专区| 亚洲国产精品VA在线看黑人| 亚洲成A∨人片在线观看不卡| 亚洲成AV人片在线观看| 亚洲精品视频专区| 国产精品亚洲专区在线观看| 国产精品亚洲自在线播放页码| 国产成人亚洲精品| 国产亚洲精品美女久久久久久下载| 亚洲精品网站在线观看不卡无广告| 色噜噜亚洲精品中文字幕| 亚洲ⅴ国产v天堂a无码二区| 亚洲春色另类小说| 亚洲国产成人无码AV在线| 亚洲美女高清一区二区三区 | 亚洲国产精品一区二区三区在线观看 | 亚洲av永久无码精品漫画| 亚洲毛片一级带毛片基地|