《大廠內部資料》Redis 性能優化的 13 條軍規!全網首發

《大廠內部資料》Redis 性能優化的 13 條軍規!全網首發
這是個人第 43 篇原創文章。
Redis 是基於單線程模型實現的,也就是 Redis 是使用一個線程來處理全部的客戶端請求的,儘管 Redis 使用了非阻塞式 IO,而且對各類命令都作了優化(大部分命令操做時間複雜度都是 O(1)),但因爲 Redis 是單線程執行的特色,所以它對性能的要求更加苛刻,本文咱們將經過一些優化手段,讓 Redis 更加高效的運行。
本文咱們將使用如下手段,來提高 Redis 的運行速度:面試

  1. 縮短鍵值對的存儲長度;
  2. 使用 lazy free(延遲刪除)特性;
  3. 設置鍵值的過時時間;
  4. 禁用長耗時的查詢命令;
  5. 使用 slowlog 優化耗時命令;
  6. 使用 Pipeline 批量操做數據;
  7. 避免大量數據同時失效;
  8. 客戶端使用優化;
  9. 限制 Redis 內存大小;
  10. 使用物理機而非虛擬機安裝 Redis 服務;
  11. 檢查數據持久化策略;
  12. 禁用 THP 特性;
  13. 使用分佈式架構來增長讀寫速度。
    1.縮短鍵值對的存儲長度

鍵值對的長度是和性能成反比的,好比咱們來作一組寫入數據的性能測試,執行結果以下:
《大廠內部資料》Redis 性能優化的 13 條軍規!全網首發
從以上數據能夠看出,在 key 不變的狀況下,value 值越大操做效率越慢,由於 Redis 對於同一種數據類型會使用不一樣的內部編碼進行存儲,好比字符串的內部編碼就有三種:int(整數編碼)、raw(優化內存分配的字符串編碼)、embstr(動態字符串編碼),這是由於 Redis 的做者是想經過不一樣編碼實現效率和空間的平衡,然而數據量越大使用的內部編碼就越複雜,而越是複雜的內部編碼存儲的性能就越低。
這還只是寫入時的速度,當鍵值對內容較大時,還會帶來另外幾個問題:redis

  • 內容越大須要的持久化時間就越長,須要掛起的時間越長,Redis 的性能就會越低;
  • 內容越大在網絡上傳輸的內容就越多,須要的時間就越長,總體的運行速度就越低;
  • 內容越大佔用的內存就越多,就會更頻繁的觸發內存淘汰機制,從而給 Redis 帶來了更多的運行負擔。
    所以在保證完整語義的同時,咱們要儘可能的縮短鍵值對的存儲長度,必要時要對數據進行序列化和壓縮再存儲,以 Java 爲例,序列化咱們可使用 protostuff 或 kryo,壓縮咱們可使用 snappy。
    2.使用 lazy free 特性

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 no
slave-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 等配置,這樣就能夠有效的提升主線程的執行效率。
    3.設置鍵值的過時時間

咱們應該根據實際的業務狀況,對鍵值設置合理的過時時間,這樣 Redis 會幫你自動清除過時的鍵值對,以節約對內存的佔用,以免鍵值過多的堆積,頻繁的觸發內存淘汰策略。
4.禁用長耗時的查詢命令安全

Redis 絕大多數讀寫命令的時間複雜度都在 O(1) 到 O(N) 之間,在官方文檔對每命令都有時間複雜度說明,地址:https://redis.io/commands
以下圖所示:
《大廠內部資料》Redis 性能優化的 13 條軍規!全網首發
其中 O(1) 表示能夠安全使用的,而 O(N) 就應該小心了,N 表示不肯定,數據越大查詢的速度可能會越慢。由於 Redis 只用一個線程來作數據查詢,若是這些指令耗時很長,就會阻塞 Redis,形成大量延時。
要避免 O(N) 命令對 Redis 形成的影響,能夠從如下幾個方面入手改造:性能優化

  • 決定禁止使用 keys 命令;
  • 避免一次查詢全部的成員,要使用 scan 命令進行分批的,遊標式的遍歷;
  • 經過機制嚴格控制 Hash、Set、Sorted Set 等結構的數據大小;
  • 將排序、並集、交集等操做放在客戶端執行,以減小 Redis 服務器運行壓力;
  • 刪除 (del) 一個大數據的時候,可能會須要很長時間,因此建議用異步刪除的方式 unlink,它會啓動一個新的線程來刪除目標數據,而不阻塞 Redis 的主線程。
    5.使用 slowlog 優化耗時命令

咱們可使用 slowlog 功能找出最耗時的 Redis 命令進行相關的優化,以提高 Redis 的運行速度,慢查詢有兩個重要的配置項:服務器

  • slowlog-log-slower-than :用於設置慢查詢的評定時間,也就是說超過此配置項的命令,將會被當成慢操做記錄在慢查詢日誌中,它執行單位是微秒 (1 秒等於 1000000 微秒);
  • slowlog-max-len :用來配置慢查詢日誌的最大記錄數。
    咱們能夠根據實際的業務狀況進行相應的配置,其中慢日誌是按照插入的順序倒序存入慢查詢日誌中,咱們可使用 slowlog get n 來獲取相關的慢查詢日誌,再找到這些慢查詢對應的業務進行相關的優化。
    6.使用 Pipeline 批量操做數據

Pipeline (管道技術) 是客戶端提供的一種批處理技術,用於一次處理多個 Redis 命令,從而提升整個交互的性能。
咱們使用 Java 代碼來測試一下 Pipeline 和普通操做的性能對比,Pipeline 的測試代碼以下:網絡

public class PipelineExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        // 記錄執行開始時間
        long beginTime = System.currentTimeMillis();
        // 獲取 Pipeline 對象
        Pipeline pipe = jedis.pipelined();
        // 設置多個 Redis 命令
        for (int i = 0; i < 100; i++) {
            pipe.set("key" + i, "val" + i);
            pipe.del("key"+i);
        }
        // 執行命令
        pipe.sync();
        // 記錄執行結束時間
        long endTime = System.currentTimeMillis();
        System.out.println("執行耗時:" + (endTime - beginTime) + "毫秒");
    }
}

以上程序執行結果爲:
執行耗時:297毫秒
普通的操做代碼以下:
public class PipelineExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 記錄執行開始時間
long beginTime = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
jedis.set("key" + i, "val" + i);
jedis.del("key"+i);
}
// 記錄執行結束時間
long endTime = System.currentTimeMillis();
System.out.println("執行耗時:" + (endTime - beginTime) + "毫秒");
}
}
以上程序執行結果爲:
執行耗時:17276毫秒
從以上的結果能夠看出,管道的執行時間是 297 毫秒,而普通命令執行時間是 17276 毫秒,管道技術要比普通的執行大約快了 58 倍。
7.避免大量數據同時失效架構

Redis 過時鍵值刪除使用的是貪心策略,它每秒會進行 10 次過時掃描,此配置可在 redis.conf 進行配置,默認值是 hz 10,Redis 會隨機抽取 20 個值,刪除這 20 個鍵中過時的鍵,若是過時 key 的比例超過 25% ,重複執行此流程,以下圖所示:app

《大廠內部資料》Redis 性能優化的 13 條軍規!全網首發

若是在大型系統中有大量緩存在同一時間同時過時,那麼會致使 Redis 循環屢次持續掃描刪除過時字典,直到過時字典中過時鍵值被刪除的比較稀疏爲止,而在整個執行過程會致使 Redis 的讀寫出現明顯的卡頓,卡頓的另外一種緣由是內存管理器須要頻繁回收內存頁,所以也會消耗必定的 CPU。
爲了不這種卡頓現象的產生,咱們須要預防大量的緩存在同一時刻一塊兒過時,就簡單的解決方案就是在過時時間的基礎上添加一個指定範圍的隨機數。
8.客戶端使用優化

在客戶端的使用上咱們除了要儘可能使用 Pipeline 的技術外,還須要注意要儘可能使用 Redis 鏈接池,而不是頻繁建立銷燬 Redis 鏈接,這樣就能夠減小網絡傳輸次數和減小了非必要調用指令。
9.限制 Redis 內存大小

在 64 位操做系統中 Redis 的內存大小是沒有限制的,也就是配置項 maxmemory <bytes> 是被註釋掉的,這樣就會致使在物理內存不足時,使用 swap 空間既交換空間,而當操心繫統將 Redis 所用的內存分頁移至 swap 空間時,將會阻塞 Redis 進程,致使 Redis 出現延遲,從而影響 Redis 的總體性能。所以咱們須要限制 Redis 的內存大小爲一個固定的值,當 Redis 的運行到達此值時會觸發內存淘汰策略,內存淘汰策略在 Redis 4.0 以後有 8 種:

  1. noeviction:不淘汰任何數據,當內存不足時,新增操做會報錯,Redis 默認內存淘汰策略;
  2. allkeys-lru:淘汰整個鍵值中最久未使用的鍵值;
  3. allkeys-random:隨機淘汰任意鍵值;
  4. volatile-lru:淘汰全部設置了過時時間的鍵值中最久未使用的鍵值;
  5. volatile-random:隨機淘汰設置了過時時間的任意鍵值;
  6. volatile-ttl:優先淘汰更早過時的鍵值。
    在 Redis 4.0 版本中又新增了 2 種淘汰策略:
  7. volatile-lfu:淘汰全部設置了過時時間的鍵值中,最少使用的鍵值;
  8. allkeys-lfu:淘汰整個鍵值中最少使用的鍵值。
    其中 allkeys-xxx 表示從全部的鍵值中淘汰數據,而 volatile-xxx 表示從設置了過時鍵的鍵值中淘汰數據。
    咱們能夠根據實際的業務狀況進行設置,默認的淘汰策略不淘汰任何數據,在新增時會報錯。
    10.使用物理機而非虛擬機
  • 在虛擬機中運行 Redis 服務器,由於和物理機共享一個物理網口,而且一臺物理機可能有多個虛擬機在運行,所以在內存佔用上和網絡延遲方面都會有很糟糕的表現,咱們能夠經過 ./redis-cli --intrinsic-latency 100 命令查看延遲時間,若是對 Redis 的性能有較高要求的話,應儘量在物理機上直接部署 Redis 服務器。
    11.檢查數據持久化策略

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 性能優化的 13 條軍規!全網首發
其中 yes 表示已經開啓混合持久化,no 表示關閉,Redis 5.0 默認值爲 yes。若是是其餘版本的 Redis 首先須要檢查一下,是否已經開啓了混合持久化,若是關閉的狀況下,能夠經過如下兩種方式開啓:
經過命令行開啓
經過修改 Redis 配置文件開啓
① 經過命令行開啓
使用命令 config set aof-use-rdb-preamble yes 執行結果以下圖所示:
《大廠內部資料》Redis 性能優化的 13 條軍規!全網首發

命令行設置配置的缺點是重啓 Redis 服務以後,設置的配置就會失效。
② 經過修改 Redis 配置文件開啓
在 Redis 的根路徑下找到 redis.conf 文件,把配置文件中的 aof-use-rdb-preamble no 改成 aof-use-rdb-preamble yes 以下圖所示:
《大廠內部資料》Redis 性能優化的 13 條軍規!全網首發

配置完成以後,須要重啓 Redis 服務器,配置才能生效,但修改配置文件的方式,在每次重啓 Redis 服務以後,配置信息不會丟失。
須要注意的是,在非必須進行持久化的業務中,能夠關閉持久化,這樣能夠有效的提高 Redis 的運行速度,不會出現間歇性卡頓的困擾。
12.禁用 THP 特性

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。
13.使用分佈式架構來增長讀寫速度

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

  • 主從同步
  • 哨兵模式
  • Redis Cluster 集羣
    使用主從同步功能咱們能夠把寫入放到主庫上執行,把讀功能轉移到從服務上,所以就能夠在單位時間內處理更多的請求,從而提高的 Redis 總體的運行速度。
    而哨兵模式是對於主從功能的升級,但當主節點奔潰以後,無需人工干預就能自動恢復 Redis 的正常使用。
    Redis Cluster 是 Redis 3.0 正式推出的,Redis 集羣是經過將數據庫分散存儲到多個節點上來平衡各個節點的負載壓力。
    Redis Cluster 採用虛擬哈希槽分區,全部的鍵根據哈希函數映射到 0 ~ 16383 整數槽內,計算公式:slot = CRC16(key) & 16383,每個節點負責維護一部分槽以及槽所映射的鍵值數據。這樣 Redis 就能夠把讀寫壓力從一臺服務器,分散給多臺服務器了,所以性能會有很大的提高。
    在這三個功能中,咱們只須要使用一個就好了,毫無疑問 Redis Cluster 應該是首選的實現方案,它能夠把讀寫壓力自動的分擔給更多的服務器,而且擁有自動容災的能力。
    【END】

往期推薦

Java14發佈!Switch竟如此簡單?Lombok也不須要了?來用Idea搭建Java14吧!

阿里巴巴Java開發手冊建議設置HashMap的初始容量,但設置多少合適呢?

由於我說:volatile 是輕量級的 synchronized,面試官讓我回去等通知!

關注下方二維碼,訂閱更多精彩內容
《大廠內部資料》Redis 性能優化的 13 條軍規!全網首發

相關文章
相關標籤/搜索