緩存:數據交換的緩沖區,存儲的數據的臨時地方,讀寫性能較高。
步驟:
- 先從redis里面查詢
- 緩存命中:直接返回結果
- 緩存未命中
- 從數據庫里面查詢
- 沒有數據:返回null
- 有數據:存到redis里面,并返回
緩存更新策略:
1、內存淘汰:redis內存不足時,自動淘汰一部分數據;
2、超時剔除:設置TTL過期時間;
3、主動更新:查詢數據庫時就更新redis。
按業務場景去使用:
低一致性:內存淘汰
高一致性:主動更新,超時剔除作為兜底
主動更新策略
- Cache Aside Pattern調用者自己編碼,更新數據庫時更新緩存
- Read/Write Through Pattern緩存和數據庫整合為一個服務,調用者使用該服務
- Write Behind Caching Pattern調用者僅僅操作緩存,由其他的線程異步將緩存數據持久化到數據庫
緩存穿透
緩存穿透的定義
緩存穿透是指客戶端請求的數據在緩存和數據庫中都不存在,這樣每次請求都會穿透緩存直接訪問數據庫。如果大量這樣的請求同時出現,可能會導致數據庫壓力過大,甚至造成數據庫服務崩潰。
例如,攻擊者故意使用一些不存在的用戶 ID 頻繁請求用戶信息接口,由于這些用戶 ID 對應的信息在緩存和數據庫中都沒有,就會使這些請求直接打到數據庫上。
緩存穿透產生的原因
惡意攻擊:攻擊者通過構造大量不存在的鍵來頻繁請求服務,試圖使數據庫過載。
業務邏輯問題:在業務代碼中,可能存在沒有對數據是否存在進行有效驗證就直接查詢數據庫的情況。比如,在一個電商系統中,商品 ID 如果沒有經過合法性檢查,可能會有一些無效的 ID 被當作正常請求發送到數據庫。
緩存穿透的解決方案
緩存空對象
原理:當從數據庫查詢數據發現不存在時,在緩存中緩存一個空對象(可以使用一個特定的標記值來表示空對象),并設置一個較短的過期時間。這樣,下次同樣的請求過來時,會在緩存中命中這個空對象,避免直接訪問數據庫。
優缺點:
優點:簡單有效,能夠快速解決大部分緩存穿透問題。
缺點:
- 如果惡意攻擊者使用大量不同的不存在的鍵進行攻擊,緩存中會存儲大量的空對象,占用緩存空空間,可以將過期時間設置的短一點。
- 會造成數據短期的不一致。id不存在,緩存了一個空值,當新的數據來時,并且和當前緩存空對象的id一致。會造成redis緩存是空,但是實際上有這條數據。當該id數據被訪問時,走redis取出的就會是空。可以給過期時間設置短一點,或者在插入數據時跟新一下緩存。
布隆過濾器(Bloom Filter)
原理:布隆過濾器是一種概率型數據結構,它可以用來判斷一個元素是否在一個集合中。它的原理是通過多個哈希函數對元素進行哈希運算,將結果對應的位在一個位數組中置為 1。當查詢一個元素是否存在時,對該元素進行同樣的哈希運算,如果所有對應的位都是 1,則該元素可能存在;如果有任何一位是 0,則該元素一定不存在。
示例:使用 Google Guava 庫中的布隆過濾器(這只是一個簡單示例,實際應用中可能需要根據數據量和誤判率等因素調整參數)。
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.nio.charset.Charset;
public class BloomFilterExample {public static void main(String[] args) {// 創建一個布隆過濾器,預計插入1000個元素,誤判率為0.01BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("UTF - 8")), 1000, 0.01);// 假設這些是數據庫中存在的用戶IDString[] existingUserIds = {"1", "2", "3"};for (String userId : existingUserIds) {bloomFilter.put(userId);}// 檢查用戶ID是否可能存在String nonExistentUserId = "4";if (!bloomFilter.mightExist(nonExistentUserId)) {System.out.println("這個用戶ID大概率不存在,無需查詢數據庫");}}
}
優缺點:
優點:可以高效地過濾掉大量不存在的元素,節省緩存空間和數據庫查詢次數。
缺點:實現復雜,存在一定的誤判率,即有可能將實際上不存在的元素判斷為可能存在,但可以通過調整參數來降低誤判率。而且布隆過濾器本身也需要占用一定的內存空間。
主動避免緩存穿透
上面的都是被動去接受緩存穿透,但是可以主動去避免。
- 增強id的復雜性,避免被猜出id;
- 對傳來的id做基本的格式校驗,將不符合要求的id給pass掉;
- 做好熱點參數的限流(微服務)
- 加強用戶權限的校驗,用戶沒有權限不能訪問一些接口。
緩存雪崩
緩存雪崩是指在同一時段大量的緩存key同時失效或者Redis服務宕機,導致大量請求到達數據庫,帶來巨大壓力。
解決方案:
- 給不同的Key的TTL添加隨機值
- 利用Redis集群提高服務的可用性
- 給緩存業務添加降級限流策略(微服務)
- 給業務添加多級緩存(微服務)
緩存擊穿
緩存擊穿問題也叫熱點Key問題,就是一個被高并發訪問并且緩存重建業務較復雜的key突然失效了,無數的請求訪問會在瞬間給數據庫帶來巨大的沖擊。
緩存重建業務較復雜 :查詢數據庫建立緩存耗時較長(幾十、幾百毫秒),在這個時間內可能有多個其他的線程也來訪問,去查數據庫,照成數據庫壓力過大。
解決方案:
互斥鎖
多個線程來訪問,當一個線程訪問未命中時,獲取鎖,然后查數據庫,建立緩存,釋放鎖;在這個時間內,其他的線程再來時,獲取不到鎖,一直等待帶線程釋放鎖。
優點:保證一致性;
缺點:
其他的許多線程會等待該線程執行完成,性能下降;
可能引發死鎖。
邏輯過期
適用于緩存熱點key,提前有緩存預熱。
把過期時間設置在 redis的value中,注意:這個過期時間并不會直接作用于redis,而是我們后續通過邏輯去處理。
假設線程1去查詢緩存,然后從value中判斷出來當前的數據已經過期了,此時線程1去獲得互斥鎖,那么其他線程會進行阻塞,獲得了鎖的線程他會開啟一個 線程2去進行 以前的重構數據的邏輯,直到新開的線程2完成這個邏輯后,才釋放鎖, 而線程1直接進行返回,假設現在線程3過來訪問,由于線程線程2持有著鎖,所以線程3無法獲得鎖,線程3也直接返回數據,只有等到新開的線程2把重建數據構建完后,其他線程才能走返回正確的數據。
這種方案巧妙在于,異步的構建緩存,缺點在于在構建完緩存之前,返回的都是臟數據。
優點:其他的線程無需等待;
缺點:不能保證數據的一致性;
緩存預熱 :活動開始時,對于一些熱點key,提前加到redis中,設置邏輯過期時間,所以說緩存一般是一定會命中的,未命中說明不是熱點key,返回null;活動結束,再刪除。