天天看點

《吐血整理》Redis 性能優化的 13 條軍規!史上最全

Redis 是基于單線程模型實作的,也就是 Redis 是使用一個線程來處理所有的用戶端請求的,盡管 Redis 使用了非阻塞式 IO,并且對各種指令都做了優化(大部分指令操作時間複雜度都是 O(1)),但由于 Redis 是單線程執行的特點,是以它對性能的要求更加苛刻,本文我們将通過一些優化手段,讓 Redis 更加高效的運作。

本文我們将使用以下手段,來提升 Redis 的運作速度:

縮短鍵值對的存儲長度;

使用 lazy free(延遲删除)特性;

設定鍵值的過期時間;

禁用長耗時的查詢指令;

使用 slowlog 優化耗時指令;

使用 Pipeline 批量操作資料;

避免大量資料同時失效;

用戶端使用優化;

限制 Redis 記憶體大小;

使用實體機而非虛拟機安裝 Redis 服務;

檢查資料持久化政策;

禁用 THP 特性;

使用分布式架構來增加讀寫速度。

鍵值對的長度是和性能成反比的,比如我們來做一組寫入資料的性能測試,執行結果如下:

從以上資料可以看出,在 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:表示當 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 會幫你自動清除過期的鍵值對,以節約對記憶體的占用,以避免鍵值過多的堆積,頻繁的觸發記憶體淘汰政策。

Redis 絕大多數讀寫指令的時間複雜度都在 O(1) 到 O(N) 之間,在官方文檔對每指令都有時間複雜度說明,位址:redis.io/commands,如下…

其中 O(1) 表示可以安全使用的,而 O(N) 就應該當心了,N 表示不确定,資料越大查詢的速度可能會越慢。因為 Redis 隻用一個線程來做資料查詢,如果這些指令耗時很長,就會阻塞 Redis,造成大量延時。

要避免 O(N) 指令對 Redis 造成的影響,可以從以下幾個方面入手改造:

決定禁止使用 keys 指令;

避免一次查詢所有的成員,要使用 scan 指令進行分批的,遊标式的周遊;

通過機制嚴格控制 Hash、Set、Sorted Set 等結構的資料大小;

将排序、并集、交集等操作放在用戶端執行,以減少 Redis 伺服器運作壓力;

删除 (del) 一個大資料的時候,可能會需要很長時間,是以建議用異步删除的方式 unlink,它會啟動一個新的線程來删除目标資料,而不阻塞 Redis 的主線程。

我們可以使用 slowlog 功能找出最耗時的 Redis 指令進行相關的優化,以提升 Redis 的運作速度,慢查詢有兩個重要的配置項:

<code>slowlog-log-slower-than</code> :用于設定慢查詢的評定時間,也就是說超過此配置項的指令,将會被當成慢操作記錄在慢查詢日志中,它執行機關是微秒 (1 秒等于 1000000 微秒);

<code>slowlog-max-len</code> :用來配置慢查詢日志的最大記錄數。

我們可以根據實際的業務情況進行相應的配置,其中慢日志是按照插入的順序倒序存入慢查詢日志中,我們可以使用 <code>slowlog get n</code> 來擷取相關的慢查詢日志,再找到這些慢查詢對應的業務進行相關的優化。

Pipeline (管道技術) 是用戶端提供的一種批處理技術,用于一次處理多個 Redis 指令,進而提高整個互動的性能。

我們使用 Java 代碼來測試一下 Pipeline 和普通操作的性能對比,Pipeline 的測試代碼如下:

以上程式執行結果為:

執行耗時:297毫秒

普通的操作代碼如下:

執行耗時:17276毫秒

從以上的結果可以看出,管道的執行時間是 297 毫秒,而普通指令執行時間是 17276 毫秒,管道技術要比普通的執行大約快了 58 倍。

Redis 過期鍵值删除使用的是貪心政策,它每秒會進行 10 次過期掃描,此配置可在 redis.conf 進行配置,預設值是 <code>hz 10</code>,Redis 會随機抽取 20 個值,删除這 20 個鍵中過期的鍵,如果過期 key 的比例超過 25% ,重複執行此流程,如下圖所示:

如果在大型系統中有大量緩存在同一時間同時過期,那麼會導緻 Redis 循環多次持續掃描删除過期字典,直到過期字典中過期鍵值被删除的比較稀疏為止,而在整個執行過程會導緻 Redis 的讀寫出現明顯的卡頓,卡頓的另一種原因是記憶體管理器需要頻繁回收記憶體頁,是以也會消耗一定的 CPU。

為了避免這種卡頓現象的産生,我們需要預防大量的緩存在同一時刻一起過期,就簡單的解決方案就是在過期時間的基礎上添加一個指定範圍的随機數。

在用戶端的使用上我們除了要盡量使用 Pipeline 的技術外,還需要注意要盡量使用 Redis 連接配接池,而不是頻繁建立銷毀 Redis 連接配接,這樣就可以減少網絡傳輸次數和減少了非必要調用指令。

在 64 位作業系統中 Redis 的記憶體大小是沒有限制的,也就是配置項 <code>maxmemory</code> 是被注釋掉的,這樣就會導緻在實體記憶體不足時,使用 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:淘汰整個鍵值中最少使用的鍵值。

其中 allkeys-xxx 表示從所有的鍵值中淘汰資料,而 volatile-xxx 表示從設定了過期鍵的鍵值中淘汰資料。

我們可以根據實際的業務情況進行設定,預設的淘汰政策不淘汰任何資料,在新增時會報錯。

在虛拟機中運作 Redis 伺服器,因為和實體機共享一個實體網口,并且一台實體機可能有多個虛拟機在運作,是以在記憶體占用上和網絡延遲方面都會有很糟糕的表現,我們可以通過 <code>./redis-cli --intrinsic-latency 100</code> 指令檢視延遲時間,如果對 Redis 的性能有較高要求的話,應盡可能在實體機上直接部署 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 之後新增了混合持久化的方式,是以我們在必須要進行持久化操作時,應該選擇混合持久化的方式。

查詢是否開啟混合持久化可以使用 <code>config get aof-use-rdb-preamble</code> 指令,執行結果如下圖所示:

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

通過指令行開啟

通過修改 Redis 配置檔案開啟

使用指令 <code>config set aof-use-rdb-preamble yes</code> 執行結果如下圖所示:

指令行設定配置的缺點是重新開機 Redis 服務之後,設定的配置就會失效。

在 Redis 的根路徑下找到 redis.conf 檔案,把配置檔案中的 <code>aof-use-rdb-preamble no</code> 改為 <code>aof-use-rdb-preamble yes</code> 如下圖所示:

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

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

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

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

echo never &gt; /sys/kernel/mm/transparent_hugepage/enabled

為了使機器重新開機後 THP 配置依然生效,可以在 /etc/rc.local 中追加 <code>echo never &gt; /sys/kernel/mm/transparent_hugepage/enabled</code>。

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

主從同步

哨兵模式

Redis Cluster 叢集

使用主從同步功能我們可以把寫入放到主庫上執行,把讀功能轉移到從服務上,是以就可以在機關時間内處理更多的請求,進而提升的 Redis 整體的運作速度。

而哨兵模式是對于主從功能的更新,但當主節點奔潰之後,無需人工幹預就能自動恢複 Redis 的正常使用。

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

Redis Cluster 采用虛拟哈希槽分區,所有的鍵根據哈希函數映射到 0 ~ 16383 整數槽内,計算公式:slot = CRC16(key) &amp; 16383,每一個節點負責維護一部分槽以及槽所映射的鍵值資料。這樣 Redis 就可以把讀寫壓力從一台伺服器,分散給多台伺服器了,是以性能會有很大的提升。

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

關注下面二維碼,訂閱更多精彩内容。

《吐血整理》Redis 性能優化的 13 條軍規!史上最全
《吐血整理》Redis 性能優化的 13 條軍規!史上最全
《吐血整理》Redis 性能優化的 13 條軍規!史上最全

關注公衆号(加好友):

《吐血整理》Redis 性能優化的 13 條軍規!史上最全

作者:

王磊的部落格

出處:

http://vipstone.cnblogs.com/