一、設計優化
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緩存淘汰策略
-
- noeviction:不淘汰任何數據,當內存不足時,新增操作會報錯,Redis 默認內存淘汰策略;
- allkeys-lru:淘汰整個鍵值中最久未使用的鍵值;
- allkeys-random:隨機淘汰任意鍵值;
- volatile-lru:淘汰所有設置了過期時間的鍵值中最久未使用的鍵值;
- volatile-random:隨機淘汰設置了過期時間的任意鍵值;
- volatile-ttl:優先淘汰更早過期的鍵值;
- volatile-lfu:淘汰所有設置了過期時間的鍵值中,最少使用的鍵值;
- 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存入緩存
