Redis 是一種內存數據庫,將數據保存在內存中,讀寫效率要比傳統的將數據保存在磁盤上的數據庫要快不少。可是 Redis 也會發生延遲時,這是就須要咱們對其產生緣由有深入的瞭解,以便於快速排查問題,解決 Redis的延遲問題redis
在本文場景下,延遲 (latency) 是指從客戶端發送命令到客戶端接收到命令返回值的時間間隔。因此咱們先來看一下 Redis 一條命令執行的步驟,其中每一個步驟出問題均可能致使高延遲。算法
上圖是 Redis 客戶端發送一條命令的執行過程示意圖,綠色的是執行步驟,而藍色的則是可能出現的致使高延遲的緣由。數據庫
網絡鏈接限制、網絡傳輸速率和CPU性能等是全部服務端均可能產生的性能問題。可是 Redis 有本身獨有的可能致使高延遲的問題:命令或者數據結構誤用、持久化阻塞和內存交換。安全
並且更爲致命的是,Redis 採用單線程和事件驅動的機制來處理網絡請求,分別有對應的鏈接應答處理器,命令請求處理器和命令回覆處理器來處理客戶端的網絡請求事件,處理完一個事件就繼續處理隊列中的下一個。一條命令處理出現了高延遲會影響接下來處於排隊狀態的其餘命令。有關 Redis 事件處理機制的能夠參考本篇文章。網絡
對於高延遲,Redis 原生提供慢查詢統計功能,執行 slowlog get {n} 命令能夠獲取最近的 n 條慢查詢命令,默認對於執行超過10毫秒(可配置)的命令都會記錄到一個定長隊列中,線上實例建議設置爲1毫秒便於及時發現毫秒級以上的命令。數據結構
# 超過 slowlog-log-slower-than 閾值的命令都會被記錄到慢查詢隊列中 # 隊列最大長度爲 slowlog-max-len slowlog-log-slower-than 10000 slowlog-max-len 128
若是命令執行時間在毫秒級,則實例實際OPS只有1000左右。慢查詢隊列長度默認128,可適當調大。慢查詢自己只記錄了命令執行時間,不包括數據網絡傳輸時間和命令排隊時間,所以客戶端發生阻塞異常 後,可能不是當前命令緩慢,而是在等待其餘命令執行。須要重點比對異常和慢查詢發生的時間點,確認是否有慢查詢形成的命令阻塞排隊。併發
slowlog的輸出格式以下所示。第一個字段表示該條記錄在全部慢日誌中的序號,最新的記錄被展現在最前面;第二個字段是這條記錄被記錄時的系統時間,能夠用 date 命令來將其轉換爲友好的格式第三個字段表示這條命令的響應時間,單位爲 us (微秒);第四個字段爲對應的 Redis 操做。app
> slowlog get 1) 1) (integer) 26 2) (integer) 1450253133 3) (integer) 43097 4) 1) "flushdb"
下面咱們就來依次看一下不合理地使用命令或者數據結構、持久化阻塞和內存交換所致使的高延遲問題。async
通常來講 Redis 執行命令速度都很是快,可是當數據量達到必定級別時,某些命令的執行就會花費大量時間,好比對一個包含上萬個元素的 hash 結構執行 hgetall 操做,因爲數據量比較大且命令算法複雜度是 O(n),這條命令執行速度必然很慢。高併發
這個問題就是典型的不合理使用命令和數據結構。對於高併發的場景咱們應該儘可能避免在大對象上執行算法複雜度超過 O(n) 的命令。對於鍵值較多的 hash 結構可使用 scan 系列命令來逐步遍歷,而不是直接使用 hgetall 來所有獲取。
Redis 自己提供發現大對象的工具,對應命令:redis-cli-h {ip} -p {port} bigkeys。這條命令會使用 scan 從指定的 Redis DB 中持續採樣,實時輸出當時獲得的 value 佔用空間最大的 key 值,並在最後給出各類數據結構的 biggest key 的總結報告。
> redis-cli -h host -p 12345 --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 hash found so far 'idx:user' with 1 fields [00.00%] Biggest hash found so far 'idx:product' with 3 fields [00.00%] Biggest hash found so far 'idx:order' with 14 fields [02.29%] Biggest hash found so far 'idx:fund' with 16 fields [02.29%] Biggest hash found so far 'idx:pay' with 69 fields [04.45%] Biggest set found so far 'indexed_word_set' with 1482 members [05.93%] Biggest hash found so far 'idx:address' with 159 fields [11.79%] Biggest hash found so far 'idx:reply' with 196 fields -------- summary ------- Sampled 1484 keys in the keyspace! Total key length in bytes is 13488 (avg len 9.09) Biggest set found 'indexed_word_set' has 1482 members Biggest hash found 'idx:的' has 196 fields 0 strings with 0 bytes (00.00% of keys, avg size 0.00) 0 lists with 0 items (00.00% of keys, avg size 0.00) 2 sets with 1710 members (00.13% of keys, avg size 855.00) 1482 hashs with 6731 fields (99.87% of keys, avg size 4.54) 0 zsets with 0 members (00.00% of keys, avg size 0.00)
對於開啓了持久化功能的Redis節點,須要排查是不是持久化致使的阻 塞。持久化引發主線程阻塞的操做主要有:fork 阻塞、AOF刷盤阻塞。
fork 操做發生在 RDB 和 AOF 重寫時,Redis 主線程調用 fork 操做產生共享內存的子進程,由子進程完成對應的持久化工做。若是 fork 操做自己耗時過長,必然會致使主線程的阻塞。
Redis 執行 fork 操做產生的子進程內存佔用量表現爲與父進程相同,理論上須要一倍的物理內存來完成相應的操做。可是 Linux 具備寫時複製技術 (copy-on-write),父子進程會共享相同的物理內存頁,當父進程處理寫請求時會對須要修改的頁複製出一份副本完成寫操做,而子進程依然讀取 fork 時整個父進程的內存快照。因此,通常來講,fork 不會消耗過多時間。
能夠執行info stats
命令獲取到 latest_fork_usec 指標,表示 Redis 最近一次 fork 操做耗時,若是耗時很大,好比超過1秒,則須要作出優化調整。
> redis-cli -c -p 7000 info | grep -w latest_fork_usec latest_fork_usec:315
當咱們開啓AOF持久化功能時,文件刷盤的方式通常採用每秒一次,後 臺線程每秒對AOF文件作 fsync 操做。當硬盤壓力過大時,fsync 操做須要等待,直到寫入完成。若是主線程發現距離上一次的 fsync 成功超過2秒,爲了數據安全性它會阻塞直到後臺線程執行 fsync 操做完成。這種阻塞行爲主要是硬盤壓力引發,能夠查看 Redis日誌識別出這種狀況,當發生這種阻塞行爲時,會打印以下日誌:
Asynchronous AOF fsync is taking too long (disk is busy). \ Writing the AOF buffer without waiting for fsync to complete, \ this may slow down Redis.
也能夠查看 info persistence 統計中的 aof_delayed_fsync 指標,每次發生 fdatasync 阻塞主線程時會累加。
>info persistence loading:0 aof_pending_bio_fsync:0 aof_delayed_fsync:0
內存交換(swap)對於 Redis 來講是很是致命的,Redis 保證高性能的一個重要前提是全部的數據在內存中。若是操做系統把 Redis 使用的部份內存換出到硬盤,因爲內存與硬盤讀寫速度差幾個數量級,會致使發生交換後的 Redis 性能急劇降低。識別 Redis 內存交換的檢查方法以下:
>redis-cli -p 6383 info server | grep process_id # 查詢 redis 進程號 >cat /proc/4476/smaps | grep Swap # 查詢內存交換大小 Swap: 0 kB Swap: 4 kB Swap: 0 kB Swap: 0 kB
若是交換量都是0KB或者個別的是4KB,則是正常現象,說明Redis進程內存沒有被交換。
有不少方法能夠避免內存交換的發生。好比說:
echo10>/proc/sys/vm/swappiness
。