Get 所有 Redis 效能問題分析手段

CrazyZard發表於2020-03-30
  • Redis 效能的基本面

  • 優化網路延時

  • 警惕執行時間長的操作

  • 優化資料結構、使用正確的演算法

  • 考慮作業系統和硬體是否影響效能

  • 考慮持久化帶來的開銷

  • 使用分散式架構 —— 讀寫分離、資料分片

  • 單執行緒
  • 多路 I/O 複用機制
    • 一個指令 1 微秒
    • 單核 CPU 一秒就能處理 1 百萬個指令(大概對應著幾十萬個請求)
  • 使用 unix 程式間通訊,如果單機部署
    • 使用 Unix 程式間通訊來請求 Redis 服務,速度比 localhost 區域網(學名 loopback)更快
  • 使用 multi-key 指令合併多個指令,減少請求數,如果有可能的話
    • 如兩個 GET key 可以用 MGET key1 key2 合併
  • 使用 transaction、script 合併 requests 以及 responses
    • MULTI/EXEC
    • Script
  • 使用 pipeline 合併 response
  • 禁止 KEYS *LRANGE mylist 0 -1
    • rename-command KEYS ''來禁止使用這個耗時的指令
  • 慢指令
    • SLOWLOG GET count 多長算長,可以通過在 redis.conf 中設定 slowlog-log-slower-than 來定義
    • DEL DEL 一個大的 object 時候,回收相應的記憶體可能會需要很長時間(甚至幾秒),所以,建議用 DEL 的非同步版本:UNLINK .會啟動一個新的 thread 來刪除目標 key,而不阻塞原來的執行緒。
    • EXPIREAT警惕使用這個指令,可能使 keys 同時過期
    • lazyfree-lazy-expire yes 把 keys 的過期刪除操作變為非同步的

一種資料型別(比如 string,list)進行增刪改查的效率是由其底層的儲存結構決定的。

我們在使用一種資料型別時,可以適當關注一下它底層的儲存結構及其演算法,避免使用複雜度太高的方法。舉兩個例子:

  1. ZADD 的時間複雜度是 O(log(N)),這比其他資料型別增加一個新元素的操作更復雜,所以要小心使用。

  2. 若 Hash 型別的值的 fields 數量有限,它很有可能採用 ziplist 這種結構做儲存,而 ziplist 的查詢效率可能沒有同等欄位數量的 hashtable 效率高,在必要時,可以調整 Redis 的儲存結構。

除了時間效能上的考慮,有時候我們還需要節省儲存空間。比如上面提到的 ziplist 結構,就比 hashtable 結構節省儲存空間(Redis Essentials 的作者分別在 hashtable 和 ziplist 結構的 Hash 中插入 500 個 fields,每個 field 和 value 都是一個 15 位左右的字串,結果是 hashtable 結構使用的空間是 ziplist 的 4 倍。)。但節省空間的資料結構,其演算法的複雜度可能很高。所以,這裡就需要在具體問題面前做出權衡。

  • CPU:Intel 多種 CPU 都比 AMD 皓龍系列好
  • 虛擬化:實體機比虛擬機器好,主要是因為部分虛擬機器上,硬碟不是本地硬碟,監控軟體導致 fork 指令的速度慢(持久化時會用到 fork),尤其是用 Xen 來做虛擬化時。
  • 記憶體管理:在 linux 作業系統中,為了讓 ranslation lookaside buffer 即 TLB,能夠管理更多記憶體空間(TLB 只能快取有限個 page),作業系統把一些 memory page 變得更大,比如 2MB 或者 1GB,而不是通常的 4096 位元組,這些大的記憶體頁叫做 huge pages。同時,為了方便程式設計師使用這些大的記憶體 page,作業系統中實現了一個 transparent huge pages(THP)機制,使得大記憶體頁對他們來說是透明的,可以像使用正常的記憶體 page 一樣使用他們。但這種機制並不是資料庫所需要的,可能是因為 THP 會把記憶體空間變得緊湊而連續吧,就像 mongodb 的文件[11]中明確說的,資料庫需要的是稀疏的記憶體空間,所以請禁掉 THP 功能。Redis 也不例外,但 Redis 官方部落格上給出的理由是:使用大記憶體 page 會使 bgsave 時,fork 的速度變慢;如果 fork 之後,這些記憶體 page 在原程式中被修改了,他們就需要被複制(即 copy on write),這樣的複製會消耗大量的記憶體(畢竟,人家是 huge pages,複製一份消耗成本很大)。所以,請禁止掉作業系統中的 transparent huge pages 功能。
  • 交換空間:當一些記憶體 page 被儲存在交換空間檔案上,而 Redis 又要請求那些資料,那麼作業系統會阻塞 Redis 程式,然後把想要的 page,從交換空間中拿出來,放進記憶體。這其中涉及整個程式的阻塞,所以可能會造成延時問題,一個解決方法是禁止使用交換空間(Redis Essentials 中如是建議,如果記憶體空間不足,請用別的方法處理)。
  • RDB(全量持久化):
    • 原程式 fork 出來一個子程式, fork 過程中redis 是無法處理請求的.要使用合理的 RDB 持久化的時間間隔,不要太頻繁。
  • AOF 增量持久化:
    • 會呼叫兩個系統呼叫,一個是 write(2),同步完成,一個是 fsync(2),非同步完成。
  • Redis 允許三種配置,選用哪種取決於你對備份及時性和效能的平衡:
  1. always:當把 appendfsync 設定為 always,fsync 會和客戶端的指令同步執行,因此最可能造成延時問題,但備份及時性最好。

  2. everysec:每秒鐘非同步執行一次 fsync,此時 redis 的效能表現會更好,但是 fsync 依然可能阻塞 write,算是一個折中選擇。

  3. no:redis 不會主動出發 fsync (並不是永遠不 fsync,那是不太可能的),而由 kernel 決定何時 fsync

  1. 把慢速的指令發到某些從庫中執行

  2. 把持久化功能放在一個很少使用的從庫上

  3. 把某些大 list 分片

Redis Cluster 批量操作嚴格要求資料在同一個slot上

  1. 多路 I/O 複用機制: https://redis.io/topics/clients#how-client...
  2. 部落格: https://redis.io/topics/latency#i39ve-litt...
  3. 詳細文件,本人只是在自己理解上做了裁剪
本作品採用《CC 協議》,轉載必須註明作者和本文連結

快樂就是解決一個又一個的問題!

相關文章