Redis 效能優化

CrazyZard發表於2020-04-02

鍵值對的長度是和效能成反比的,比如我們來做一組寫入資料的效能測試,執行結果如下:
Redis 效能優化的 13 條軍規
在 key 不變的情況下,value 值越大操作效率越慢,因為 Redis 對於同一種資料型別會使用不同的內部編碼進行儲存,比如字串的內部編碼就有三種:int(整數編碼)、raw(優化記憶體分配的字串編碼)、embstr(動態字串編碼),這是因為 Redis 的作者是想通過不同編碼實現效率和空間的平衡,然而資料量越大使用的內部編碼就越複雜,而越是複雜的內部編碼儲存的效能就越低
這還只是寫入時的速度,當鍵值對內容較大時,還會帶來另外幾個問題:

  • 內容越大需要的持久化時間就越長,需要掛起的時間越長,Redis 的效能就會越低;

  • 內容越大在網路上傳輸的內容就越多,需要的時間就越長,整體的執行速度就越低;

  • 內容越大佔用的記憶體就越多,就會更頻繁的觸發記憶體淘汰機制,從而給 Redis 帶來了更多的執行負擔。

因此在保證完整語義的同時,我們要儘量的縮短鍵值對的儲存長度,必要時要對資料進行序列化和壓縮再儲存,以 Java 為例,序列化我們可以使用 protostuff 或 kryo,壓縮我們可以使用 snappy。

lazy free 特性是 Redis 4.0 新增的一個非常使用的功能,它可以理解為惰性刪除或延遲刪除。意思是在刪除的時候提供非同步延時釋放鍵值的功能,把鍵值釋放操作放在 BIO(Background I/O) 單獨的子執行緒處理中,以減少刪除刪除對 Redis 主執行緒的阻塞,可以有效地避免刪除 big key 時帶來的效能和可用性問題
lazy free 對應了 4 種場景,預設都是關閉的:

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del
noslave-lazy-flush no

它們代表的含義如下:

  • lazyfree-lazy-eviction:表示當 Redis 執行記憶體超過 maxmeory 時,是否開啟 lazy free 機制刪除;

  • lazyfree-lazy-expire:表示設定了過期時間的鍵值,當過期之後是否開啟 lazy free 機制刪除;

  • lazyfree-lazy-server-del:有些指令在處理已存在的鍵時,會帶有一個隱式的 del 鍵的操作,比如 rename 命令,當目標鍵已存在,Redis 會先刪除目標鍵,如果這些目標鍵是一個 big key,就會造成阻塞刪除的問題,此配置表示在這種場景中是否開啟 lazy free 機制刪除;

  • slave-lazy-flush:針對 slave(從節點) 進行全量資料同步,slave 在載入 master 的 RDB 檔案前,會執行 flushall 來清理自己的資料,它表示此時是否開啟 lazy free 機制刪除。

建議開啟其中的 lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-server-del 等配置,這樣就可以有效的提高主執行緒的執行效率。

我們應該根據實際的業務情況,對鍵值設定合理的過期時間,這樣 Redis 會幫你自動清除過期的鍵值對,以節約對記憶體的佔用,以避免鍵值過多的堆積,頻繁的觸發記憶體淘汰策略。

避免 O(N) 命令對 Redis 造成的影響

  • 決定禁止使用 keys 命令;
  • 避免一次查詢所有的成員,要使用 scan 命令進行分批的,遊標式的遍歷;
  • 通過機制嚴格控制 Hash、Set、Sorted Set 等結構的資料大小;
  • 將排序、並集、交集等操作放在客戶端執行,以減少 Redis 伺服器執行壓力;
  • 刪除 (del) 一個大資料的時候,可能會需要很長時間,所以建議用非同步刪除的方式 unlink,它會啟動一個新的執行緒來刪除目標資料,而不阻塞 Redis 的主執行緒。

使用 slowlog 功能找出最耗時的 Redis 命令進行相關的優化

  • slowlog-log-slower-than :用於設定慢查詢的評定時間,也就是說超過此配置項的命令,將會被當成慢操作記錄在慢查詢日誌中,它執行單位是微秒 (1 秒等於 1000000 微秒);
  • slowlog-max-len :用來配置慢查詢日誌的最大記錄數。
    慢日誌是按照插入的順序倒序存入慢查詢日誌中,我們可以使用 slowlog get n 來獲取相關的慢查詢日誌
$res = Redis::pipeline(function($pipe) use($params) {
    for ($i = 0; $i < 1000; $i++) {
        $pipe->set("key:$i", $params);
    }
});

Redis 過期鍵值刪除使用的是貪心策略,它每秒會進行 10 次過期掃描,此配置可在 redis.conf 進行配置,預設值是 hz 10,Redis 會隨機抽取 20 個值,刪除這 20 個鍵中過期的鍵,如果過期 key 的比例超過 25% ,重複執行此流程
如果在大型系統中有大量快取在同一時間同時過期,那麼會導致 Redis 迴圈多次持續掃描刪除過期字典,直到過期字典中過期鍵值被刪除的比較稀疏為止,而在整個執行過程會導致 Redis 的讀寫出現明顯的卡頓,卡頓的另一種原因是記憶體管理器需要頻繁回收記憶體頁,因此也會消耗一定的 CPU。
解決方案就是在過期時間的基礎上新增一個指定範圍的隨機數

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

在 64 位作業系統中 Redis 的記憶體大小是沒有限制的,這樣就會導致在實體記憶體不足時,使用 swap 空間既交換空間,而當操心繫統將 Redis 所用的記憶體分頁移至 swap 空間時,將會阻塞 Redis 程式,導致 Redis 出現延遲,從而影響 Redis 的整體效能。
因此我們需要限制 Redis 的記憶體大小為一個固定的值,當 Redis 的執行到達此值時會觸發記憶體淘汰策略,記憶體淘汰策略在 Redis 4.0 之後有 8 種

  • noeviction:不淘汰任何資料,當記憶體不足時,新增操作會報錯,Redis 預設記憶體淘汰策略;
  • allkeys-lru:淘汰整個鍵值中最久未使用的鍵值;
  • allkeys-random:隨機淘汰任意鍵值;
  • volatile-lru:淘汰所有設定了過期時間的鍵值中最久未使用的鍵值;
  • volatile-random:隨機淘汰設定了過期時間的任意鍵值;
  • volatile-ttl:優先淘汰更早過期的鍵值。

在 Redis 4.0 版本中又新增了 2 種淘汰策略:

  • volatile-lfu:淘汰所有設定了過期時間的鍵值中,最少使用的鍵值;
  • allkeys-lfu:淘汰整個鍵值中最少使用的鍵值。

在虛擬機器中執行 Redis 伺服器,因為和物理機共享一個物理網口,並且一臺物理機可能有多個虛擬機器在執行,因此在記憶體佔用上和網路延遲方面都會有很糟糕的表現,我們可以通過 ./redis-cli --intrinsic-latency 100 命令檢視延遲時間,如果對 Redis 的效能有較高要求的話,應儘可能在物理機上直接部署 Redis 伺服器。

Redis 4.0 之後,Redis 有 3 種持久化的方式

  • RDB(Redis DataBase,快照方式)將某一個時刻的記憶體資料,以二進位制的方式寫入磁碟;
  • AOF(Append Only File,檔案追加方式),記錄所有的操作命令,並以文字的形式追加到檔案中;
  • 混合持久化方式,Redis 4.0 之後新增的方式,混合持久化是結合了 RDB 和 AOF 的優點,在寫入的時候,先把當前的資料以 RDB 的形式寫入檔案的開頭,再將後續的操作命令以 AOF 的格式存入檔案,這樣既能保證 Redis 重啟時的速度,又能減低資料丟失的風險。

RDB 和 AOF 持久化各有利弊,RDB 可能會導致一定時間內的資料丟失,而 AOF 由於檔案較大則會影響 Redis 的啟動速度,為了能同時擁有 RDB 和 AOF 的優點,Redis 4.0 之後新增了混合持久化的方式,因此我們在必須要進行持久化操作時,應該選擇混合持久化的方式。
查詢是否開啟混合持久化可以使用 config get aof-use-rdb-preamble

Redis 效能優化

其中 yes 表示已經開啟混合持久化,no 表示關閉,Redis 5.0 預設值為 yes。如果是其他版本的 Redis 首先需要檢查一下,是否已經開啟了混合持久化,如果關閉的情況下,可以通過以下兩種方式開啟:

  • 通過命令列開啟
    • config set aof-use-rdb-preamble yes
  • 通過修改 Redis 配置檔案開啟
    • 在 Redis 的根路徑下找到 redis.conf 檔案,把配置檔案中的 aof-use-rdb-preamble no 改為 aof-use-rdb-preamble yes

配置完成之後,需要重啟 Redis 伺服器,配置才能生效,但修改配置檔案的方式,在每次重啟 Redis 服務之後,配置資訊不會丟失。

需要注意的是,在非必須進行持久化的業務中,可以關閉持久化,這樣可以有效的提升 Redis 的執行速度,不會出現間歇性卡頓的困擾

Linux kernel 在 2.6.38 核心增加了 Transparent Huge Pages (THP) 特性 ,支援大記憶體頁 2MB 分配,預設開啟。

當開啟了 THP 時,fork 的速度會變慢,fork 之後每個記憶體頁從原來 4KB 變為 2MB,會大幅增加重寫期間父程式記憶體消耗。同時每次寫命令引起的複製記憶體頁單位放大了 512 倍,會拖慢寫操作的執行時間,導致大量寫操作慢查詢。例如簡單的 incr 命令也會出現在慢查詢中,因此 Redis 建議將此特性進行禁用,禁用方法如下:

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

為了使機器重啟後 THP 配置依然生效,可以在 /etc/rc.local 中追加 echo never > /sys/kernel/mm/transparent_hugepage/enabled

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

  • 主從同步

  • 哨兵模式

  • Redis Cluster 叢集

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

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

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

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

以上文章來源於Java中文社群 ,作者老王 ,本人根據個人理解做了摘要

本作品採用《CC 協議》,轉載必須註明作者和本文連結

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

相關文章