總結篇:redis 典型緩存架構設計問題及性能優化

redis 典型緩存架構設計問題及性能優化總結:

緩存穿透

查詢一個根本不存在的數據,緩存層和存儲層都不會命中。通常出於容錯的考慮,如果從存儲層查不到數據,則不寫入緩存層。

原因:

自身業務代碼或數據有問題
惡意攻擊等造成大量空命中
解決方案1:緩存空對象

解決方案2:布隆過濾器

當布隆過濾哭喊 說某個值存在時,這個值可能不存在。當說它不存在時,那就肯定不存在。

對於不存在的數據布隆過濾器一般都能過濾掉,不再讓請求再往後端發送。

布隆過濾器就是一個大型的位數組和幾個不一樣的無偏hash 函數,所謂無偏就是能夠把元素的hash 值算得比較均勻。

這種方法適用於數據命中不高、數據相對穩定、實時性低的應用場景,通常是數據集較大,代碼維護較爲複雜,但是緩存空間佔用較少。

緩存擊穿

大量緩存同時失效導致請求同時穿透緩存直達數據庫,可能會造成數據庫瞬間壓力過大掛掉。最好將這一批數據的緩存過期時間設置爲一個時間段內的不同時間。

int expireTime - new Random().nextInt(300) + 300;

緩存血崩

如果緩存架構設計得不好,大量請求訪問bigkey,導致緩存能支撐的併發急劇下降,大量請求都會打到存儲層,造成存儲層也會級聯宕機的情況。 解決問題:

1 保證緩存層服務高可用性,比如使用redis Sentinel 或 redis Cluster
2 依賴隔離組件爲後端限流熔斷並降級。比如使用 Sentinel 或 Hystrix 限流降級組件。
我們可以針對不同的數據採取不同的處理方式。當業務應用訪問的是非核心數據,如商品屬性,用戶信息等,暫時停止從緩存中查詢這些數據,而是直接返回預定義的默認降級信息、空值或是錯誤提示信息;當業務應用訪問的是核心數據,如商品庫存,仍然允許查詢緩存,如果緩存缺失,可以繼續通過數據庫讀取。

3 做好數據容災。提前演練,並做一些預案。
熱點數據緩存優化

使用“緩存+過期時間”的策略既可以加速數據讀寫,又保證數據的定期更新,這種模式基本能夠滿足絕大部分需求。

但是這種策略存在的問題,對應用卻是致命的。

當前key 是一個熱點key, 如熱門活動,併發量非常大
重建緩存不能在短時間完成,可能是一個複雜計算,例如繁雜的SQL,多次IO, 多個依賴等,在緩存操作新選的瞬間,有大量純種來重建緩存,造成後端負載加大,甚至可能會讓應用崩潰。
解決這個問題,就是要避免大量純種同時重建緩存。

可以利用互斥鎖,此方法只允許一個純種重建緩存,其他線程等待重建緩存的線程執行完,重新從緩存獲取數據。

String get(String key) {

// 從redis 中獲取數據
String value = redis.get(key);

// 如果value 爲空,則重構緩存
if(null == value){
    // 只允許一個線程重建緩存,使用nx, 並設置過期時間ex
    String mutexKey = "mutext:key:" + key;
    
    if(redis.set(mutexKey,"1","ex 180",nx)){
        // 從數據庫中取數據
        value = db.get(key);
        // 設置過期時間
        redis.setex(key,timeout,value);
        // 刪除key_mutex
        redis.delete(mutexKey);
    } else {
        Thread.sleep(50);
        
        get(key);
    }
    
}
return value;

}

緩存數據庫讀寫不一致

1 雙寫不一致
2 讀寫不一致
解決方案:

1 對於併發機率很小的數據,如個人維度的訂單數據,用戶數據等,這種幾乎不用考慮這個問題,很少會發生緩存不一致,可以給緩存數據加上過期時間,每隔一段時間觸發讀的主動更新即可。
2 就算併發很高,如果業務上能容忍短時間的緩存數據不一致,如商品名稱,商品分類菜單等,緩存加上過期時間依然可以解決大部分業務對於緩存的要求。
3 如果不能容忍緩存數據不一致,可以通過讀寫鎖保證併發讀寫或寫寫的時間按順序排好隊,讀讀的時間相當於無鎖。
4 也可以用阿里開源的canal 通過監聽數據庫的binlog 日誌及時的去修改緩存,但是引入了新的中間件,增加了系統的複雜度。
小結:一般針對是讀多寫少的情況加入緩存提高性能,如果寫多讀多的情況又不能容忍緩存數據不一致,那就沒必要加緩存了,可以直接操作數據庫。如果數據庫抗不住壓力,還可以把緩存作爲數據讀寫的主存儲,異步將數據同步到數據庫,數據庫只是作爲數據的備份。

加入緩存的數據庫應該是對實時性、一致性要求不是很高的數據,切記不要爲了用緩存,同時又要保證絕對的一致性做大量的過度設計和控制,增加系統的複雜性。

開發規範

key 名設計

1 建議:可讀性和可管理性
以業務名或數據庫名庫前綴,防止key 衝突,用冒號分隔。如 業務名:表名:id

2 建議:簡潔性
保證語義的前提下,控制key 長度,當key 較多時,內存佔用也不容忽視。

3 強制:不要包含特殊字符
反例:空格,換行,單雙引號以及其他轉義字符

value 設計

1 強制:拒絕bigkey ,防止網卡流量,慢查詢
在redis 中,一個字符串最大512M, 一個二級數據結構可以存儲大約40億(2^32 -1)個元素,但是實際中如果有下面兩種情況,我們認爲它是bigkey。

1 字符串類型:它的big 體現在單個value 值很大,一般認爲超過10KB 就是bigkey。
2 非字符串類型:hash, list,set, zset,它們的big 體現在元素個數太多。
一般來說,String 類型控制在10kb 以內, hash, list, set, zset 元素個數不要超過5000。非字符串的bigkey,不要使用del 刪除,使用hscan,sscan,zscan 方式浙進式刪除,同時要注意防止bigkey 過期時間自動刪除問題。(如一個200w 的zset 設置一個小時過期,會觸發 del 操作,造成阻塞)

bigkey 性能優化

1 bigkey 的產生

一般來說,bigkey 的產生都是由於程序設計不當,或者對於數據規模預料不清楚造成的。

1 社交類:大V 粉絲列表,如果設計不當,必是bigkey
2 統計類:如按天存儲某項功能或者網站的用戶集合,除非沒人用,否則必是bigkey
3 緩存類:將數據從數據庫中load 出來序列化放在redis 中,但是要注意1-是不是有必要把所有字段都緩存;2-有沒有相關關聯的數據,爲圖方便而產生關聯數據, 產生bigkey.
2 如何優化

1 拆

big list : list1, list2, ... , listN

big hash :可以將數據分段存儲,比如一個大的key, 假設存了100w 的用戶數據,可以拆分成200個key, 每個key 下面5000個用戶數據。

2 推薦:選擇適合的數據類型

舉例:

// 正例:
hmset user:1 name tom age 20 favor swimming

// 反例:
set user 1 : name tom
set user 1 : age 20
set user 1 : favor swimming

3 推薦:控制key 的生命週期

建議使用expire 設置過期時間,同時過期時間要隨機,防止集中過期。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章