Redis缓存雪崩、缓存穿透、热点Key
我們通常使用 緩存 + 過期時間的策略來幫助我們加速接口的訪問速度,減少了后端負載,同時保證功能的更新。
1、緩存穿透
緩存系統,按照KEY去查詢VALUE,當KEY對應的VALUE一定不存在的時候并對KEY并發請求量很大的時候,就會對后端造成很大的壓力。
(查詢一個必然不存在的數據。比如文章表,查詢一個不存在的id,每次都會訪問DB,如果有人惡意破壞,很可能直接對DB造成影響。)
由于緩存不命中,每次都要查詢持久層。從而失去緩存的意義。
1.1 使用互斥鎖排隊
業界比價普遍的一種做法,即根據key獲取value值為空時,鎖上,從數據庫中load數據后再釋放鎖。若其它線程獲取鎖失敗,則等待一段時間后重試。這里要注意,分布式環境中要使用分布式鎖,單機的話用普通的鎖(synchronized、Lock)就夠了。
public String getWithLock(String key, Jedis jedis, String lockKey,String uniqueId, long expireTime) {// 通過key獲取valueString value = redisService.get(key);if (StringUtil.isEmpty(value)) {// 分布式鎖,詳細可以參考https://blog.csdn.net/fanrenxiang/article/details/79803037// 封裝的tryDistributedLock包括setnx和expire兩個功能,在低版本的redis中不支持try {boolean locked = redisService.tryDistributedLock(jedis,lockKey, uniqueId, expireTime);if (locked) {value = userService.getById(key);redisService.set(key, value);redisService.del(lockKey);return value;} else {// 其它線程進來了沒獲取到鎖便等待50ms后重試Thread.sleep(50);getWithLock(key, jedis, lockKey, uniqueId, expireTime);}} catch (Exception e) {log.error("getWithLock exception=" + e);return value;} finally {redisService.releaseDistributedLock(jedis, lockKey, uniqueId);}}return value;}這樣做思路比較清晰,也從一定程度上減輕數據庫壓力,但是鎖機制使得邏輯的復雜度增加,吞吐量也降低了,有點治標不治本。
1.2 布隆過濾器
bloomfilter就類似于一個hash set,用于快速判某個元素是否存在于集合中,其典型的應用場景就是快速判斷一個key是否存在于某容器,不存在就直接返回。布隆過濾器的關鍵就在于hash算法和容器大小,下面先來簡單的實現下看看效果,我這里用guava實現的布隆過濾器:
<dependencies> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency> </dependencies> public class BloomFilterTest {private static final int capacity = 1000000;private static final int key = 999998;private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), capacity);static {for (int i = 0; i < capacity; i++) {bloomFilter.put(i);}}public static void main(String[] args) {/* 返回計算機最精確的時間,單位微妙 */long start = System.nanoTime();if (bloomFilter.mightContain(key)) {System.out.println("成功過濾到" + key);}long end = System.nanoTime();System.out.println("布隆過濾器消耗時間:" + (end - start));int sum = 0;for (int i = capacity + 20000; i < capacity + 30000; i++) {if (bloomFilter.mightContain(i)) {sum = sum + 1;}}System.out.println("錯判率為:" + sum);}}成功過濾到999998 布隆過濾器消耗時間:215518 錯判率為:318 復可以看到,100w個數據中只消耗了約0.2毫秒就匹配到了key,速度足夠快。然后模擬了1w個不存在于布隆過濾器中的key,匹配錯誤率為318/10000,也就是說,出錯率大概為3%,跟蹤下BloomFilter的源碼發現默認的容錯率就是0.03
2、緩存雪崩問題
緩存在同一時間內大量鍵過期(失效),接著來的一大波請求瞬間都落在了數據庫中導致連接異常。
解決方案:
3、熱點key
(1) 這個key是一個熱點key(例如一個重要的新聞,一個熱門的八卦新聞等等),所以這種key訪問量可能非常大。
(2) 緩存的構建是需要一定時間的。(可能是一個復雜計算,例如復雜的sql、多次IO、多個依賴(各種接口)等等)
于是就會出現一個致命問題:在緩存失效的瞬間,有大量線程來構建緩存(見下圖),造成后端負載加大,甚至可能會讓系統崩潰 。
解決方法:使用互斥鎖(mutex key):這種解決方案思路比較簡單,就是只讓一個線程構建緩存,其他線程等待構建緩存的線程執行完,重新從緩存獲取數據就可以了
"提前"使用互斥鎖(mutex key):在value內部設置1個超時值(timeout1), timeout1比實際的memcache timeout(timeout2)小。當從cache讀取到timeout1發現它已經過期時候,馬上延長timeout1并重新設置到cache。然后再從數據庫加載數據并設置到cache中。
“永遠不過期”:
這里的“永遠不過期”包含兩層意思:
(1) 從redis上看,確實沒有設置過期時間,這就保證了,不會出現熱點key過期問題,也就是“物理”不過期。(2) 從功能上看,如果不過期,那不就成靜態的了嗎?所以我們把過期時間存在key對應的value里,如果發現要過期了,通過一個后臺的異步線程進行緩存的構建,也就是“邏輯”過期4、緩存和數據庫間數據一致性問題
分布式環境下(單機就不用說了)非常容易出現緩存和數據庫間的數據一致性問題,針對這一點的話,只能說,如果你的項目對緩存的要求是強一致性的,那么請不要使用緩存。我們只能采取合適的策略來降低緩存和數據庫間數據不一致的概率,而無法保證兩者間的強一致性。合適的策略包括 合適的緩存更新策略,更新數據庫后要及時更新緩存、緩存失敗時增加重試機制,例如MQ模式的消息隊列。
5、拓展
http://www.mobabel.net/總結redis熱點key發現及常見解決方案/
總結
以上是生活随笔為你收集整理的Redis缓存雪崩、缓存穿透、热点Key的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小米绿豆粥的功效与作用、禁忌和食用方法
- 下一篇: 第k个排列