redis06

緩存的使用與設計前端

 


1.受益node

加速讀寫
CPU L1/L2/L3 Cache、瀏覽器緩存、Ehcache緩存數據庫結果
下降後端負載
後端服務器經過前端緩存下降負載:業務端使用Redis下降後端MySQL的負載redis

 

2.成本算法

數據不一致:緩存層和數據層有時間窗口不一致問題,和更新策略有關
代碼維護成本:多了一層緩存邏輯
運維成本:例如Redis Cluster數據庫

 

3.使用場景後端

下降後端負載
對高消耗的SQL:join結果集/分組統計結果緩存
加速請求響應
利用Redis/Memcache優化IO響應時間
大量寫合併爲批量寫
入計數器先Redis累加再批量寫DB瀏覽器

 

緩存的更新策略緩存

 

LRU等算法剔除:例如 maxmemory-policy服務器

 

2.超時剔除:例如expire網絡

3.主動更新:開發控制生命週期

4.兩條建議
低一致性數據:最大內存和淘汰策略
高一致性:超時剔除和主動更新結合,最大內存和淘汰策略兜底

 

緩存粒度問題

通用性:全量屬性更好
佔用空間:部分屬性更好
代碼維護:表面上全量屬性更好

 

 

 

緩存穿透優化

查詢一個不存在的數據,因爲緩存是不命中時須要從數據庫查詢,查不到數據則不寫入緩存,這將致使這個不存在的數據每次請求都要到數據庫去查詢

產生緣由
業務代碼自身問題
惡意攻擊、爬蟲
發現問題
業務的響應時間,受到惡意攻擊時,廣泛請求被打到存儲層,必會引發響應時間提升,可經過監控發現

業務自己問題
相關指標:總調用數、緩存層命中數、存儲層命中數

解決方法1:緩存空對象(設置過時時間)

當存儲層查詢不到數據後,往cache層中存儲一個null,後期再被查詢時,能夠經過cache返回null。
缺點
cache層須要存儲更多的key
緩存層和數據層數據「短時間」不一致

 1 public String getPassThrough(String key) {
 2     String cacheValue = cache.get(key);
 3     if( StringUtils.isEmpty(cacheValue) ) {
 4         String storageValue = storage.get(key);
 5         cache.set(key , storageValue);
 6         if( StringUtils.isEmpty(storageValue) ) {
 7             cache.expire(key , 60 * 5 );
 8         } 
 9         return storageValue;
10     } else {
11         return cacheValue;
12     }
13 }

 

 

解決方法2:布隆過濾器攔截(適合固定的數據)

將全部可能存在的數據哈希到一個足夠大的bitmap中,一個不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力

 

 

 

緩存雪崩優化

 

因爲cache服務器承載大量的請求,當cache服務異常脫機,流量直接壓向後端組件,形成級聯故障。或者緩存集中在一段時間內失效,發生大量的緩存穿透

解決方法1:保證緩存高可用性

作到緩存多節點、多機器、甚至多機房。
Redis Cluster、Redis Sentinel
作二級緩存

解決方法2:依賴隔離組件爲後端限流

使用Hystrix作服務降級

解決方法3:提早演練(壓力測試)

解決方法4:對不一樣的key隨機設置過時時間

 

 

無底洞問題

 

添加機器時,客戶端的性能不但沒提高,反而降低
關鍵點
更多的機器 != 更高的性能
更多的機器 = 數據增加與水平擴展
批量接口需求:一次mget隨着機器增多,網絡節點訪問次數更多。網絡節點的時間複雜度由O(1) -> O(node)

優化IO的方法
命令自己優化:減小慢查詢命令:keys、hgetall、查詢bigKey並進行優化
減小網絡通訊次數
mget由O(keys),升級爲O(node),O(max_slow(node)) , 甚至是O(1)
下降接入成本:例如客戶端長鏈接/鏈接池、NIO等

 

 

熱點Key的重建優化

 

熱點Key(訪問量比較大) + 較長的重建時間(重建過程當中的API或者接口比較費時間)
致使的問題:有大量的線程會去查詢數據源並重建緩存,對存儲層形成了巨大的壓力,響應時間會變得很慢

 

 

1.三個目標

減小重建緩存的次數
數據儘量一致
減小潛在危險:例如死鎖、線程池大量被hang住(懸掛)

 

2.兩種解決方案

互斥鎖(分佈式鎖)
第一個線程須要重建時候,對這個Key的重建加入分佈式鎖,重建完成後進行解鎖
這個方法避免了大量的緩存重建與存儲層的壓力,可是仍是會有大量線程的阻塞

jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime)

String get(String key) {
    String SET_IF_NOT_EXIST = "NX";
    String SET_WITH_EXPIRE_TIME = "PX";
    
    String value = jedis.get(key);
    if( null == value ) {
        String lockKey = "lockKey:" + key;
        if( "OK".equals(jedis.set(lockKey , "1" , SET_IF_NOT_EXIST , 
                      SET_WITH_EXPIRE_TIME , 180)) ) {
            value = db.get(key);
            jedis.set(key , value);
            jedis.delete(lockKey);
        } else {
            Thread.sleep(50);
            get(key);
        }
    }
    return value;
}

 

永遠不過時

緩存層面:不設置過時時間(不使用expire)
功能層面:爲每一個value添加邏輯過時時間,單發現超過邏輯過時時間後,會使用單獨的線程去重建緩存。
還存在一個數據不一致的狀況。能夠將邏輯過時時間相對實際過時時間相對減少

 

 

Redis規模化的困擾

發佈構建繁瑣,私搭亂蓋
節點&機器等運維成本
監控報警比較低級

 

 

CacheCloud的主要功能

一鍵開啓Redis(單點、Sentinel、Cluster)
機器、應用、實例監控和報警
客戶端:透明使用、性能上報
可視化運維:配置、擴容、Failover、機器/應用/實例上下線
已存在Redis直接移入和數據遷移

 

 

布隆過濾器引出

問題引出:現有50億個電話號碼,有10萬個電話號碼,要快速準確判斷這些電話號碼是否已經存在
1. 經過數據庫查詢:實現快速有點難
2. 數據預先放在內存集合中:50億 * 8字節 = 40GB(***內存***浪費或不夠)
3. hyperloglog:準確有點難

 

 

布隆過濾器原理

須要參數
m個二進制向量(m個二進制位數)
n個預備數據(相似於問題中的50億個電話號碼)
k個hash函數(每一個數據,逐個進行hash,將指定向量標識爲1)
構建布隆過濾器
將n個數據逐個走一遍hash流程
判斷元素是否存在
將元素逐個hash進行執行
若是獲得的hash結果再向量中全都是1,則代表存在,反之當前元素不存在

 

 

布隆過濾器偏差率

對的數據的返回結果必然是對的,可是錯誤的數據也多是對的
直觀因素
m / n 的比例:比例越大,偏差率越小
hash函數的個數:個數越多,偏差率越小


本地布隆過濾器

實現類庫:Guava

 1 Funnel<Integer> funnel = Funnels.integerFunnel();
 2 int size = 1000_000;
 3 double errorChance = 0.001;        //錯誤率
 4 BloomFilter<Integer> filter = BloomFilter.create(funnel , size , errorChance);
 5 for(int i = 0 ; i < size ; i++ ) {
 6     filter.put(i);
 7 }
 8 for(int i = 0 ; i < size ; i++ ) {
 9     if( !filter.mightContain(i) ) {
10         System.out.println("發現不存在的數據 : " + i);
11     }
12 }

 

布隆過濾器的問題

容量受到限制
多個應用存在多個布隆過濾器,構建同步過濾器複雜

 

 

Redis單機布隆過濾器

基於bitmap實現,利用setbit、getbit命令實現
hash函數:
MD5
murmur3_128
murmur3_32
sha1
sha256
sha512
基於Redis單機實現存在的問題
速度慢:與本機比,輸在網絡
解決方法1:單獨部署、與應用同機房甚至同機架部署
解決方法2:使用pipeline
容量限制:Redis最大字符串爲512MB、Redis單機容量
解決方法:基於RedisCluster實現

 

Redis Cluster實現布隆過濾器

多個布隆過濾器:二次路由
基於pipeline提升效率

 

 

內存管理

 

Redis內存消耗


內存使用統計

info memory

 

 

 2.內存消耗劃分

used_memory的組成部分
自身內存(800K左右)
緩衝內存
客戶端緩衝區
複製緩衝區
AOF緩衝區
對象內存
Key對象
Value對象
Lua內存
內存碎片 = used_memory_rss - used_memory(申請內存的會超過使用內存,內存預留與內存暫未釋放)

 

3.子進程內存消耗

 

redis在bgsave與bgrewriteaof時候都會使用fork建立出子進程,fork是copy-on-write的,當發生寫操做時,就會複製出一分內存
優化方式
去除THP特性:在kernel 2.6.38中新增的特性。這個特性能夠加快fork的速度,可是在複製內存頁的時候,會比原來的內存頁擴大了512倍。例如原來是4K,擴大後爲2M。當寫入量比較大時,會形成沒必要要的阻塞與內存的暴增。
overcommit_memory=1 能夠保證fork順利完成

 

 

 

客戶端緩衝區

 #查看已鏈接客戶端的基本信息

info clients

 

 

#查看全部Redis-Server的全部客戶端的詳細信息
client list

 

 

輸入緩衝區

各個客戶端執行的Redis命令會存儲在Redis的輸入緩衝區中,由單線程排隊執行
- 注意:輸入緩衝區最大1GB,超事後會被強制斷開,不可動態配置

 

輸出緩衝區

1 client-output-buffer-limit ${class} ${hard limit} ${soft limit} ${soft seconds}
2 ## class 客戶端類型
3 ##        normal        普通客戶端
4 ##        slave        從節點用於複製,假裝成客戶端
5 ##        pubsub        發佈訂閱客戶端
6 ## hardLimit 若是客戶端使用的輸出緩衝區大於hardLimit,客戶端會被當即關閉
7 ## softLimit 若是客戶端使用的輸出緩衝區超過了softLimit而且持續了softSeconds秒,客戶端會當即關閉
8 ## 其中hardLimit與softLimit爲0時不作任何限制

 

 

1.普通客戶端

默認配置:client-output-buffer-limit normal 0 0 0
注意:須要防止大命令或者monitor,這兩種狀況會致使輸出緩衝區暴增

2.slave客戶端

默認配置:client-output-buffer-limit slave 256mb 64mb 60
建議調大:可能主從延遲較高,或者從節點數量過多時,主從複製容易發生阻塞,緩衝區會快速打滿。最終致使全量複製
注意:在主從網絡中,從節點不要超過2個

3.pubsub客戶端

默認配置:client-output-buffer-limit pubsub 32mb 8mb 60
阻塞緣由:生產速度大於消費速度
注意:須要根據實際狀況進行調試

 

 

緩衝內存

 

1.複製緩衝區

此部份內存獨享,默認1MB,考慮部分複製,能夠設置更大,能夠設置爲100MB,犧牲部份內存避免全量複製。


2.AOF緩衝區

不管使用always、everysecs仍是no的策略,都會進行刷盤,刷盤前的數據存放在AOF緩衝區中。

另外在AOF重寫期間,會分配一塊AOF重寫緩衝區,重寫時的寫數據會存放在重寫緩衝區當中。

爲了不數據不一致的發生,在重寫完成後,會將AOF重寫緩衝區的內容,同步到AOF緩衝區中。

AOF相關的緩衝區沒有容量限制。

 

 

對象內存

Key:不要過長,量大不容忽視,建議控制在39字節以內。(embstr)
Value:儘可能控制其內部編碼爲壓縮編碼。
內存碎片
在jemalloc中必然存在內存碎片
緣由
與jemalloc有關,jemalloc會將內存空間劃分爲小、大、巨大三個範圍;每一個範圍又劃分了許多小的內存塊單位;存儲數據的時候,會選擇大小最合適的內存塊進行存儲。

redis做者爲了更好的性能,在redis中實現了本身的內存分配器來管理內存,不用的內存不會立刻返還給OS,從而實現提升性能。
修改cache的值,且修改後的value與原先的value大小差別較大。

 

優化方法

重啓redis服務,
redis4.0以上能夠設置自動清理 config set activedefrag yes,也能夠經過memory purge手動清理,配置監控使用性能最佳。
修改分配器(不推薦,須要對各個分配器十分了解)
避免頻繁更新操做

 

內存回收策略

 

1.刪除過時鍵值

惰性刪除
1. 訪問key
2. expired dict
3. del key
定時刪除:每秒運行10次,採樣刪除

2.maxmemory-policy

Noevition:默認策略,不會刪除任何數據,拒絕全部寫入操做並返回錯誤信息。
volatile-lru:根據lru算法刪除設置了超時屬性的key,直到騰出空間爲止。若是沒有可刪除的key,則將退回到noevition。
allkeys-lru:根據lru算法刪除key,無論數據有沒有設置超時屬性,直到騰出足夠空間爲止。
allkeys-random:隨機刪除全部鍵,直到騰出足夠空間爲止。
volatile-random:隨機刪除過時鍵,直到騰出足夠空間爲止。
volatile-ttl:根據鍵值對象的ttl屬性,刪除最近將要過時的數據。若是沒有,回退到noevicition。

 

 

客戶端緩衝區優化

 

案例:一次線上事故,主從節點配置的maxmemory都是4GB,發現主節點的使用內存達到了4GB即內存已打滿。而從節點只有2GB。


思考方向
考慮Redis的內存自身組成
主從節點以前數據傳輸不一致(會致使對象內存不一致)
dbsize
查看info replication中的slave_repl_offset
查看緩衝區方面
經過info clients查看最大的緩衝區佔用
經過client list查看全部客戶端的詳細信息

 

問題發現:

存在一個monitor客戶端對命令進行監聽,因爲Redis的QPS極高,monitor客戶端沒法及時處理,佔用緩衝區。
問題預防
運維層面:線上禁用monitor(rename monitor "")
運維層面:適度限制緩衝區大小
開發層面:理解monitor原理,能夠短暫尋找熱點key
開發層面:使用CacheCloud能夠直接監控到相關信息

 

內存優化其餘建議

不要忽視key的長度:1個億的key,每一個字節都是節省
序列化和壓縮方法:拒絕Java原生序列化,能夠採用Protobuf、kryo等

 

不建議使用Redis的場景

數據:大且冷的數據
功能性:關係型查詢、消息隊列

 

 

 

 

相關文章
相關標籤/搜索