首長,Redis效能最佳化十三條軍規立好了,請過目~

ITPUB社群發表於2022-11-30

前言

Redis作為高效能的記憶體資料庫,在大資料量的情況下也會遇到效能瓶頸,日常開發中只有時刻謹記最佳化鐵則,才能使得Redis效能發揮到極致。

本文將會介紹十三條效能最佳化軍規,開發過程中只要按照執行,效能必能質的飛躍。

1. 避免慢查詢命令

慢查詢命令指的是執行較慢的命令,Redis自身提供了許多的命令,並不是所有的命令都慢,這和命令的操作複雜度有關,因此必須知道Redis不同命令的複雜度。

如說,Value 型別為 String 時,GET/SET 操作主要就是操作 Redis 的雜湊表索引。這個操作複雜度基本是固定的,即 O(1)。但是,當 Value 型別為 Set 時,SORTSUNION/SMEMBERS 操作複雜度分別為 O(N+M*log(M))O(N)。其中,NSet 中的元素個數,MSORT 操作返回的元素個數。這個複雜度就增加了很多。Redis 官方文件中對每個命令的複雜度都有介紹,當你需要了解某個命令的複雜度時,可以直接查詢。

當你發現 Redis 效能變慢時,可以透過 Redis 日誌,或者是 latency monitor 工具,查詢變慢的請求,根據請求對應的具體命令以及官方文件,確認下是否採用了複雜度高的慢查詢命令。

如果確實存在大量的慢查詢命令,建議如下兩種方式:

  1. 用其他高效的命令代替:比如說,如果你需要返回一個 SET 中的所有成員時,不要使用 SMEMBERS 命令,而是要使用 SSCAN 多次迭代返回,避免一次返回大量資料,造成執行緒阻塞。

  2. 當你需要執行排序、交集、並集操作時,可以在客戶端完成,而不要用 SORTSUNION、SINTER 這些命令,以免拖慢 Redis 例項。

2. 生產環境禁用keys命令

keys這個命令是最容易忽略的慢查詢命令,因為keys命令需要遍歷儲存的鍵值對,所以操作延時很高,在生產環境使用很可能導致Redis阻塞;因此不建議在生產環境中使用keys命令

3. keys需要設定過期時間

Redis作為記憶體資料庫,一切的資料都是在記憶體中,一旦記憶體佔用過大則會大大影響效能,因此需要對有時間限制的資料需要設定過期時間,這樣Redis能夠定時的刪除過期的資料。

4. 禁止批次的給keys設定相同的過期時間

預設情況下,Redis 每 100 毫秒會刪除一些過期 key,具體的演算法如下:

  1. 取樣 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 個數的 key,並將其中過期的 key 全部刪除;
  2. 如果超過 25%key 過期了,則重複刪除的過程,直到過期 key 的比例降至 `25%`` 以下。

ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 是 Redis 的一個引數,預設是 20,那麼,一秒內基本有 200 個過期 key 會被刪除。這一策略對清除過期 key、釋放記憶體空間很有幫助。如果每秒鐘刪除 200 個過期 key,並不會對 Redis 造成太大影響。

但是,如果觸發了上面這個演算法的第二條,Redis 就會一直刪除以釋放記憶體空間。注意,刪除操作是阻塞的(Redis 4.0 後可以用非同步執行緒機制來減少阻塞影響)。所以,一旦該條件觸發,Redis 的執行緒就會一直執行刪除,這樣一來,就沒辦法正常服務其他的鍵值操作了,就會進一步引起其他鍵值操作的延遲增加,Redis 就會變慢。

頻繁使用帶有相同時間引數的 EXPIREAT 命令設定過期 key 將會觸發演算法第二條,這就會導致在一秒記憶體在大量的keys過期。

因此開發中一定要禁止批次的給keys設定過期時間。

5. 謹慎選擇資料結構

Redis 常用的資料結構一共有五種:stringhashlistsetzset(sorted set)。可以發現,大多數場景下使用 string 都可以去解決問題。但是,這並不一定是最優的選擇。下面,簡單說明下它們各自的適用場景:

  1. string:單個的快取結果,不與其他的 KV 之間有聯絡
  2. hash:一個 Object 包含有很多屬性,且這些屬性都需要單獨儲存。注意:這種情況不要使用 string,因為 string 會佔據更多的記憶體
  3. list:一個 Object 包含很多資料,且這些資料允許重複、要求有順序性
  4. set:一個 Object 包含很多資料,不要求資料有順序,但是不允許重複
  5. zset:一個 Object 包含很多資料,且這些資料自身還包含一個權重值,可以利用這個權重值來排序

另外Redis還提供了幾種的擴充套件型別,如下:

  1. HyperLogLog:適合用於基數統計,比如PV,UV的統計,存在誤差問題,不適合精確統計。
  2. BitMap:適合二值狀態的統計,比如簽到打卡,要麼打卡了,要麼未打卡。

6. 檢查持久化策略

Redis4.0之後使用瞭如下三種持久化策略:

  1. AOF日誌:一種採用檔案追加的方式將命令記錄在日誌中的策略,針對同步和非同步追加還提供了三個配置項,有興趣的可以檢視官方文件。
  2. RDB快照:以快照的方式,將某一個時刻的記憶體資料,以二進位制的方式寫入磁碟。
  3. AOFRDB混用:Redis4.0新增的方式,為了採用兩種方式各自的優點,在RDB快照的時間段內使用的AOF日誌記錄這段時間的操作的命令,這樣一旦發生當機,將不會丟失兩段快照中間的資料。

由於寫入磁碟有IO效能瓶頸,因此不是將Redis作為資料庫的話(可以從後端恢復),建議禁用持久化或者調整持久化策略。

7. 採用高速的固態硬碟作為日誌寫入裝置

由於AOF日誌的重寫對磁碟的壓力較大,很可能會阻塞,如果需要使用到持久化,建議使用高速的固態硬碟作為日誌寫入裝置。

8. 使用物理機而非虛擬機器

由於虛擬機器增加了虛擬化軟體層,與物理機相比,虛擬機器本身就存在效能的開銷,可以使用如下命令來分別測試下物理機和虛擬機器的基線效能

./redis-cli --intrinsic-latency 120

測試結果可以知道,使用物理機的基線效能明顯比虛擬機器的基線效能更好。

9. 增加機器記憶體或者使用Redis叢集

物理機器的記憶體不足將會導致作業系統記憶體的Swap

記憶體 swap 是作業系統裡將記憶體資料在記憶體和磁碟間來回換入和換出的機制,涉及到磁碟的讀寫,所以,一旦觸發 swap,無論是被換入資料的程式,還是被換出資料的程式,其效能都會受到慢速磁碟讀寫的影響。

Redis 是記憶體資料庫,記憶體使用量大,如果沒有控制好記憶體的使用量,或者和其他記憶體需求大的應用一起執行了,就可能受到 swap 的影響,而導致效能變慢

這一點對於 Redis 記憶體資料庫而言,顯得更為重要:正常情況下,Redis 的操作是直接透過訪問記憶體就能完成,一旦 swap 被觸發了,Redis 的請求操作需要等到磁碟資料讀寫完成才行。而且,和我剛才說的 AOF 日誌檔案讀寫使用 fsync 執行緒不同,swap 觸發後影響的是 Redis 主 IO 執行緒,這會極大地增加 Redis 的響應時間。

因此增加機器的記憶體或者使用Redis叢集能夠有效的解決作業系統記憶體的Swap,提高效能。

10. 使用 Pipeline 批次運算元據

Pipeline (管道技術) 是客戶端提供的一種批處理技術,用於一次處理多個 Redis 命令,從而提高整個互動的效能。

11. 客戶端使用最佳化

在客戶端的使用上我們除了要儘量使用 Pipeline 的技術外,還需要注意要儘量使用 Redis 連線池,而不是頻繁建立銷燬 Redis 連線,這樣就可以減少網路傳輸次數和減少了非必要呼叫指令。

12. 使用分散式架構來增加讀寫速度

Redis 分散式架構有三個重要的手段:

  1. 主從同步
  2. 哨兵模式
  3. Redis Cluster 叢集

使用主從同步功能我們可以把寫入放到主庫上執行,把讀功能轉移到從服務上,因此就可以在單位時間內處理更多的請求,從而提升的 Redis 整體的執行速度。

而哨兵模式是對於主從功能的升級,但當主節點奔潰之後,無需人工干預就能自動恢復 Redis 的正常使用。

Redis ClusterRedis 3.0 正式推出的,Redis 叢集是透過將資料分散儲存到多個節點上,來平衡各個節點的負載壓力。

Redis Cluster 採用虛擬雜湊槽分割槽,所有的鍵根據雜湊函式對映到 0 ~ 16383整數槽內,計算公式:slot = CRC16(key) & 16383,每一個節點負責維護一部分槽以及槽所對映的鍵值資料。這樣 Redis 就可以把讀寫壓力從一臺伺服器,分散給多臺伺服器了,因此效能會有很大的提升。

在這三個功能中,我們只需要使用一個就行了,毫無疑問 Redis Cluster 應該是首選的實現方案,它可以把讀寫壓力自動的分擔給更多的伺服器,並且擁有自動容災的能力。

13. 避免記憶體碎片

頻繁的新增修改會導致記憶體碎片的增多,因此需要時刻的清理記憶體碎片。

Redis提供了INFO memory可以檢視記憶體的使用資訊,如下:

INFO memory
# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G

mem_fragmentation_ratio:1.86

這裡有一個 mem_fragmentation_ratio 的指標,它表示的就是 Redis 當前的記憶體碎片率。那麼,這個碎片率是怎麼計算的呢?其實,就是上面的命令中的兩個指標 used_memory_rssused_memory 相除的結果。

mem_fragmentation_ratio = used_memory_rss/ used_memory

used_memory_rss 是作業系統實際分配給 Redis 的實體記憶體空間,裡面就包含了碎片;而 used_memory 是 Redis 為了儲存資料實際申請使用的空間。

那麼,知道了這個指標,我們該如何使用呢?在這兒,我提供一些經驗閾值:

  1. mem_fragmentation_ratio 大於 1 但小於 1.5。這種情況是合理的。這是因為,剛才我介紹的那些因素是難以避免的。畢竟,內因的記憶體分配器是一定要使用的,分配策略都是通用的,不會輕易修改;而外因由 Redis 負載決定,也無法限制。所以,存在記憶體碎片也是正常的。

  2. mem_fragmentation_ratio 大於 1.5 。這表明記憶體碎片率已經超過了 50%。一般情況下,這個時候,我們就需要採取一些措施來降低記憶體碎片率了。

一旦記憶體碎片率過高了,此時就應該採用手段清理記憶體碎片了,具體如何清理,參考文章:Redis清理記憶體碎片

總結

本文著重介紹了13條效能最佳化軍規,在開發過程中還是需要針對性的具體問題具體分析,希望作者這篇文章能夠幫助到你。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2925894/,如需轉載,請註明出處,否則將追究法律責任。

相關文章