隨著互聯網的越來越普及,用戶越來越多,系統性能瓶頸成了越來越熱門的話題。要解決性能問題的技術手段有很多,比如:緩存、CDN加速、頁面靜態化、集群、分布式、異步等。
緩存通常被作為首先技術方案,簡單而且提升效果明顯,它能夠將速度提升100倍。那么問題來了,緩存為啥會怎么快呢?
因為傳統的數據庫操作是基于磁盤的,而緩存是基于內存的,內存操作和磁盤操作的速度根本不是一個數量級的。目前市面上主流的緩存有:redis 和 memcache,這兩個都是基于內存的緩存技術,二者的區別我在這里暫時不講。使用緩存的偽代碼一般如下:
String order = redisClient.get(key);
if(order != null) {
return order;
}
order = db.get(key);
redisClient.put(key,order);
redisClient.expire(key,3000);
return order;
根據key獲取數據,先從緩存中查一下有沒有,如果有則直接返回。如果沒有,再從數據庫中查到數據,然后將數據放入緩存中,并且給當前key設置一個失效時間,下次再用同樣的key來請求數據時,就能夠直接從緩存中查詢到并返回,減少請求數據庫的頻次,提升性能,因為數據庫連接是稀有資源。
那么問題又來了,為啥要設置失效時間,不設置不行嗎?
著名的2/8原則告訴我們,經常訪問的數據集中在20%,而另外的80%屬于不常用數據。我們都知道內存相當于磁盤來說價格是比較昂貴的,不信你買個500G的硬盤 和 一個 500G的內存試試。既然這么貴,我們應該節約使用,所以才會有設置失效時間這種策略,一旦檢測到某個key超過了失效時間,就會將該key從緩存中刪除,可以節約內存。
還有個問題:如果在某個key失效的時候,有大量的請求一起過來會怎么樣?
這就是我今天要給大家講的:擊穿。
大量的請求訪問同一個key,剛好那個key失效了,那么同一時間所有的請求,都會穿過緩存,直接請求數據庫,此時的數據庫有可能因為無法扛著這么大的并發,直接掛了。
再問一下:如果大量的請求訪問多個key,剛好key同時失效了會怎么樣?
這就是我今天要給大家講的:雪崩。
雪崩比上面的擊穿更嚴重,擊穿只是一個key失效了,大量請求直接訪問數據庫都有可能把數據庫搞掛,更何況大量的key同時失效的場景,數據庫面臨的壓力更大,更有可能掛掉。
接下來的問題:如果大量的用戶請求緩存中不存在的key又會怎么樣?
這就是我今天要給大家講的:穿透。
有大量的請求訪問時,只有少部分的key在緩存中存在,而有大量的key不存在,這樣請求也會直接訪問到數據庫,也會導致數據庫扛不住壓力而掛掉。這種情況往往是黑客偽造請求,發起的惡意攻擊。
那么,這些問題有沒有解決辦法呢?
首先,擊穿的解決辦法-加鎖。
偽代碼如下:
String order = redisClient.get(key);
if(order != null) {
return order;
}
lock() {
String order = redisClient.get(key);
if(order != null) {
return order;
}
order = db.get(key);
redisClient.put(key,order);
redisClient.expire(key,3000);
}
return order;
如果根據key從緩存中查詢不到數據,需要從數據庫中查詢數據的時候,加一把鎖,保證同一時間只有一個線程可以查詢數據庫,然后把查詢出來的結果放回到緩存中。這樣其他的線程再用相同的key查詢時,就可以直接從緩存中查到數據。這樣就能夠極大的減少數據庫的訪問頻次。
其次,雪崩的解決辦法- 加鎖 + key設置不同的失效時間。
加鎖的偽代碼跟上面是一樣的我就不寫了。
雪崩還有一個必要條件就是在同一時間,有大量的key同時失效。我們只要保證不會出現同一時間有大量的key同時失效就可以了,每個key設置不同的失效時間就能解決問題。
最后,穿透的解決辦法- 業務規則過濾 + 布隆過濾器
業務規則過濾 可以校驗 key的長度或者比如前綴SD開頭的等,過濾一批非法數據。
接下來看看布隆過濾器:
布隆過濾器中會初始化數據庫中key的標識。如果有大量請求訪問不存在的key時,先通過布隆過濾器檢查一下key在數據庫中是否存在,如果存在才允許訪問數據庫。如果不存在,則直接返回,這樣就可以過濾掉大量的非法請求。