- 爲什麼要理解Redi緩存問題
(1) 在高併發的業務場景下,數據庫大多數情況都是用戶併發訪問最薄弱的環節。所以,就需要使用redis做一個緩衝操作,讓請求先訪問到redis,而不是直接訪問mysql等數據庫。這樣可以大大緩解數據庫的壓力
(2) 當緩存庫出現時,必須要考慮如下問題
① 緩存穿透
② 緩存穿擊
③ 緩存雪崩
④ 緩存污染(或者慢了)
⑤ 緩存和數據庫一致性
- 緩存穿透
(1) 原因
① 緩存穿透是指緩存和數據庫中都沒有數據,而用戶不斷髮起請求。由於緩存時不命中時被動寫的,並且出於容錯考慮,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要去存儲層查詢,失去了緩存的意義。在流量比較大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊應用,這就是漏洞;如果發起id=-1的數據或者id特別大不存在的數據。這時的用戶很可能是攻擊者,攻擊會導致數據庫壓力過大。
(2) 解決方案
① 接口增增加校驗,如用戶鑑權校驗,id做基礎校驗,id<=0的直接攔截
② 從緩存取不到的數據,在數據庫中也沒有取到,這個也可以將key-value對寫成key-null,緩存有效時間可以短點。這樣可以防止攻擊用戶反覆用同一個id暴力攻擊
③ 布隆過濾器。bloomfilter就類似於一個hash set,用於快速判斷某個元素是否存在於集合中,其典型的應用場景就是快速判斷一個key是否存在於某容器,不存在就直接返回。布隆過濾器的關鍵就在於hash算法和容器大小
- 緩存擊穿
(1) 問題來源
① 緩存擊穿是指緩存中沒有但是數據庫中有數據(一般是緩存時間到期),這時由於併發用戶特別多,同時讀緩存又沒有讀到數據,又同時去數據庫取數據,引起數據庫壓力瞬間增大,造成過大壓力
(2) 解決方案
① 設置熱點數據永不過期
② 接口限流與熔斷、降級。重要的接口一定要做好限流策略,防止用戶惡意刷接口,同時要降級準備,當接口中的某些服務不可用時,進行熔斷、失敗快速返回機制
③ 加互斥鎖
- 緩存雪崩
(1) 問題來源
① 緩存雪崩是指緩存中數據大批量到過期時間,而查詢數據量巨大,引起數據庫壓力過大甚至宕機。和緩存擊穿不同的是,緩存擊穿指併發查同一條數據,緩存雪崩是不同的數據都過期了,很多數據都查不到而查數據庫了
(2) 解決方案
① 緩存數據的過期時間選擇隨機,防止同一時間大量數據過期現象發生
② 如果緩存數據庫是分佈式緩存,將熱點數據均勻分佈在不同的緩存數據庫中
③ 設置熱點數據永不過期
- 緩存污染(或滿了)
(1) 緩存污染問題說的是緩存中一些只會被訪問一次或者幾次的數據,被訪問完後,再也不會被訪問到,但是這些數據依然存留在緩存中,消耗緩存空間
(2) 緩存污染會隨着數據的持續增加而逐漸顯露,隨着服務的不斷運行,緩存中會存在大量的永遠不會被再次訪問的數據。緩存空間是有限的,如果緩存空間滿了,再往緩存裏寫數據時就會又額外開銷,影響Redis性能。這部分額外開銷主要是指寫的時候判斷淘汰策略,根據淘汰策略去選擇要淘汰的數據,然後進行刪除操作
- 最大緩存設置多大
(1) 系統的設計選擇是一個權衡的過程;大容量緩存是能帶來性能加速的收益,但是成本也會更高,而小容量緩存不一定就起不到加速訪問的效果。一般來說建議把緩存容量設置爲總數據量的15%-30%,兼顧訪問性能和內存空間開銷
(2) 對於Redis來說,一旦確定了最大緩存容量,比如4GB,就可以通過命令來設置
(3) CONFIG SET maxmemory 4gb
- 數據庫和緩存一致性
(1) 問題來源
① 使用Redis做一個緩衝操作哦,讓請求前訪問到redis,而不是直接訪問mysql等數據庫
讀取緩存步驟一般沒有什麼問題,但是一旦涉及到數據更新:數據庫和緩存更新,就容易出現緩存和數據庫之間的數據一致性問題
不管是先寫mysql數據庫,再刪除Redis緩存;還是先刪除緩存再寫庫,都可能出現數據不一致情況
② 實例
1) 如果刪除了緩存Redis,還沒有來得及寫庫mysql,另外一個線程就來讀取,發現緩存爲空,則去數據庫中讀取數據寫入緩存,此時緩存中爲髒數據
2) 如果先寫了庫,再刪除緩存前,寫庫的線程宕機了,沒有刪除掉緩存,則也會出現數據不一致的情況
3) 因爲讀和寫是併發的,沒法保證順序,就會出現緩存 和數據庫的數據不一致問題
- 四種相關模式
(1) 分類
① Cache aside
② Read through
③ Write through
④ Write behind caching
(2) 最常用的Cache aside,總結
① 讀的時候 先讀緩存,緩存沒有的話,就讀數據庫,然後讀出數據後放入緩存,同時返回響應
② 更新的時候 先更新數據庫,然後在刪除緩存
③ 其具體邏輯如下
1) 失效:應用程序先從cache取數據,沒有得到,則從數據庫中取數據,成功後放入到緩存中
2) 命中:應用程序從cache中取數據,取到後返回
3) 更新:先把數據存儲到數據庫中,成功後再讓緩存失效
4) 圖示
④ 具體實現方案1:隊列+重試機制
1) 圖示
2) 流程如下:
- 更新數據庫數據
- 緩存因爲種種問題刪除失敗
- 將需要刪除的key發送到消息隊列
- 自己消費消息,獲得需要刪除的key
- 繼續重試刪除操作,直到成功
3) 缺點
- 對業務線代碼造成大量入侵
⑤ 具體實現方案2:異步更新緩存(基於訂閱binlog的同步機制)
1) 圖示
2) 技術整體思路
- Mysql binlog增量訂閱消費+消息隊列+增量數據更新到Redis
a) 讀Redis:熱點數據基本都在Redis中
b) 寫Mysql:增刪改都是操作Mysql
c) 更新Redis數據:Mysql的數據操作binlog,來更新到Redis
3) Redis更新
- 數據操作主要分爲兩大塊
a) 一個是全量(將所有數據一次性寫入到redis中)
b) 一個是增量(實時更新)
- 讀取binlog後分析,利用消息隊列,推送更新各臺的redis緩存數據
a) 這樣一旦Mysql中產生了新的寫入、更新、刪除等操作,就可以把binlog相關的消息推送至Redis,Redis再根據binlog中的 記錄,對Redis進行更新。其實這種機制,很類似於Mysql的主從備份機制,因爲Mysql也通過binlog來實現數據的一致性
b) 消息推送工具:canal、kafka、rabbitMQ等