Redis性能調優


一、設計優化

  1. 估算Redis內存使用量

  以非數字的字符串鍵值對為例,假設key和value的長度均為12個字節,則內部使用的編碼方式為embstr。共計90000個鍵值對占用的空間

  Redis中存儲鍵值對使用字典,字典內部使用哈希表數組,數組的每個元素dictEntry中共有三個指針(指向鍵的指針,指向值的指針,指向下一個節點的指針),在64位系統中,每個指針占用8字節,則共計24個字節,向上取2的整數冪,則分配32個字節。

  一個key,使用SDS存儲,數據大小12字節,len+alloc+flags+空字符共4個字節(3.2版本之后),共計12+4=16個字節

  一個value,外層使用對象redisObject並指向一個SDS(存放值內容)。對象內存占用16個字節,SDS需要16個字節

  綜上,一個dictEntry使用的內存總共為 32 + 16 + 16 + 16 = 80字節。

  存儲90000個鍵值對需要的bucket數組大小為90000向上取2的整數冪,即131072;每個bucket元素占用8字節(因為內部存儲的指針)。

  存儲90000個鍵值對占用的總內存:90000*80 + 131072*8 = 82488576。

  當存儲的鍵值對長度由12字節增加到13字節,對應的SDS變成17字節,jemalloc分配32個字節,因此每個dictEntry占用的字節數變成112字節。則存儲90000個的內存占用變為 90000*12 + 131072*8  = 11128576。

  2. 優化內存占用

    1. 利用jemalloc特性進行優化

    

    jemalloc是Redis的默認內存分配器,在64位系統中,將內存空間划分成小、大、巨大三個范圍;每個范圍又划分為許多小的內存塊單位,當Redis存儲數據時,選擇適合的內存塊進行存儲。譬如存儲130字節的對象,jemalloc會將其放入到160字節的內存單元中。

    2. 使用整型/長整型

    Redis存儲字符串的編碼類型有三種,當字符串為數字時,使用int(8字節)存儲代替字符串,可以節省很多空間。

    3. 共享對象

    共享對象可以減少對象的創建,包括redisObject的創建。Redis中的共享對象目前只有0-9999,可以通過REDIS_SHARED_INTEGERS參數提高,譬如調整到20000,則0-19999都可以共享

    4.縮短鍵值對的存儲長度

    大鍵值對,延長寫入和讀取耗時、延長持久化需要時間,延長網絡傳輸時間,並且占用內存多,更容易觸發內存淘汰機制。盡量縮短存儲長度,必要時進行壓縮和序列化

二、設置鍵值的過期時間

  Redis的serverCron函數定期清除過期鍵,節約內存占用,避免鍵值對過多堆積,頻繁觸發內存淘汰機制

三、限制Redis內存大小

  在64位系統中,默認沒有設置最大內存,配置項maxmemory被注釋了。當物理內存不足時,使用磁盤作為虛擬內存,將物理內存中的部分數據存放到虛擬內存,這個操作會阻塞Redis進程。當設置了最大內存,當超出限制時,觸發內存淘汰。內存淘汰策略在Redis4.0后有8種,主要用到以下原理

  • LRU(Least Recently Used,最近最少使用)原理:使用鏈表保存緩存數據,越靠近表頭,存放的數據訪問時間越近。當有新數據時插入表頭,當有緩存命中時,將數據移至表頭,當內存不足時,丟棄表尾數據

    缺點是類似全表掃描時,會將鏈表數據污染

  • LFU(Least Frequently Used,最不經常使用策略)原理:記錄內存塊的使用次數,回收時,按照訪問次數排序,當緩存不足時,將使用頻率最低的內存釋放。

    缺點是短時間內大量訪問的數據很難刪除

  1. Redis緩存淘汰策略

    1. noeviction:不淘汰任何數據,當內存不足時,新增操作會報錯,Redis 默認內存淘汰策略;
    2. allkeys-lru:淘汰整個鍵值中最久未使用的鍵值;
    3.  allkeys-random:隨機淘汰任意鍵值;
    4. volatile-lru:淘汰所有設置了過期時間的鍵值中最久未使用的鍵值;
    5. volatile-random:隨機淘汰設置了過期時間的任意鍵值;
    6. volatile-ttl:優先淘汰更早過期的鍵值;
    7. volatile-lfu:淘汰所有設置了過期時間的鍵值中,最少使用的鍵值;
    8. allkeys-lfu:淘汰整個鍵值中最少使用的鍵值;

 四、使用Lazy free特性(Redis4.0新增)

  刪除大鍵值對比較耗時,造成主線程的阻塞,為此將刪除的操作放在子線程中。共有四項配置:

  • lazyfree-lazy-eviction:當Redis運行內存超過最大內存,是否啟用lazy free
  • lazyfree-lazy-expire:當設置了過期鍵,在鍵過期之后,是否啟用lazy free
  • lazyfree-lazy-server-del:有些命令會隱式刪除鍵,比如rename命令,對這些命令執行時是否啟用lazy free
  • slave-lazy-flush:從節點加載主節點的RDB文件前,會運行flushall清理原有數據,此時是否啟用lazy free

五、禁用長耗時的查詢命令

  Redis大部分的讀寫命令的時間復雜度在O(1)到O(N)之間。對於O(N)的命令,需要謹慎使用,如果執行時間過長,將會阻塞Redis

  • 禁用Keys
  • 避免一次查詢所有鍵,使用scan命令進行分批遍歷
  • 控制Hash、Set、Sorted Set結構的數據大小
  • 將排序、並集、交集放到客戶端進行
  • 刪除大數據,使用unlink,啟用新線程刪除目標數據(Redis 6.0啟用多線程的原因,增加I/O操作並發)

六、使用slowlog優化耗時命令

  使用slowlog命令找出高耗時的Redis命令。慢查詢的配置項:

  • slowlog-log-slower-than:慢查詢評定的時間閾值,單位微妙
  • slowlog-max-len:配置慢查詢日志的最大記錄數

七、避免大量數據同時失效

  serverCron函數每100毫秒執行一次過期掃描。隨機抽取過期鍵字典中的20個鍵,刪除其中的已經過期的鍵,判斷過期鍵的比例是否超過25%,重復執行此流程。如果一次掃描中刪除了大量過期鍵,將會造成阻塞。

  在設置過期時間時,加入隨機數。

八、檢查數據持久化策略

  Redis4.0之后,加入混合持久化功能,結合了RDB和AOF。在寫入時,將當前數據以RDB的形式寫入文件的開頭,后續的操作命令以AOF的格式存入文件;在加載時,先加載RDB文件,再加載AOF命令。

  RDB持久化,可能存在一定時間內的數據丟失。AOF持久化,文件較大時執行較慢,影響啟動速度。在非必須持久化操作時,可以關閉持久化,避免間歇性的卡頓(serverCron函數周期性執行持久化操作)

九、使用Pipeline批量操作數據

  

 

十、客戶端使用優化

  使用Redis連接池,減少網絡傳輸次數和非必要調用指令。

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

 

十一、使用分布式架構增加讀寫速度

  Redis分布式架構有:

  • 主從同步:讀寫分離
  • 哨兵:自動容災
  • Redis Cluster集群:多讀多寫,高擴展(集群可添加節點)高可用(某主節點無從節點自動將其他主節點多余的從節點轉移),自動容災

  詳細:https://www.jianshu.com/p/f0c01c528d8d

十二、其他優化

  使用物理機非虛擬機。虛擬機和物理機共享物理網口,並且一台物理機可能有多個虛擬機運行,在內存占用和網絡延遲上性能較差

十三、禁用THP特性

  Linux默認開啟,支持大內存頁2MB分配。

  開啟THP之后,fork速度變慢,fork之后每個內存頁從4KB變成2MB,大幅增加重寫期間父進程內存消耗。同時每次寫命令引起的復制內存頁單位放大了512倍,會拖慢寫操作的執行時間,導致大量寫操作慢查詢。

 

Redis雪崩現象:

  產生條件:1. 大量緩存同時失效 2.大量並發請求訪問失效緩存 導致數據庫宕機

  解決方案:過期時間設置加入隨機數

Redis緩存擊穿:

  緩存中沒有(過期),但數據庫中存在數據。此時大量並發請求訪問這部分數據,數據庫壓力陡增。緩存雪崩是大量的緩存擊穿。

  解決方案:

    1.設置熱點數據永不過期

    2.接口限流熔斷和降級

    3.布隆過濾器。bloomfilter類似一個哈希表但是不存key,快速判斷一個元素是否在集合中。使用多個哈希函數計算入參key得到坐標,在一個bit數組中存儲對應位置為1。查找時只要有一個位置為0則不存在,但都為1也有可能不存在。使用多個哈希函數是基於哈希沖突的考量。

    4.加鎖。這種情況,只允許能有一個線程查詢數據庫,獲取結果后將結果放入緩存,其余線程則直接訪問緩存

public String getData(String key) throws Exception{

    String data = redis.get(key);
    if(data == null){
        if(lock.tryLock()){
            data = redis.get(key);
            if(data == null){
                //查詢數據庫
                data = mysql.select();
                redis.set(key,data);
            }else{
                return data;
            }
        }else{            
            Thread.sleep(1000);
            return getData(key);
        }
    }
    return data;        
}

 

 

Redis緩存穿透:

  緩存和數據庫中都沒有某數據,但有大量的並發請求查詢這些數據。導致數據庫宕機,主要考慮是漏洞攻擊

  解決方案:

    1. 接口層加校驗,譬如用戶鑒權

    2. 將對應的key的value設置為null存入緩存

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM