高併發緩存常見問題總結

1、緩存穿透
緩存穿透是指請求查詢數據,在數據庫沒有,自然在緩存中也不會有。這樣就導致用戶查詢的時候,在緩存中找不到,每次都要去數據庫再查詢一遍,然後返回空(相當於進行了兩次無用的查詢)。
這樣請求就繞過緩存直接查數據庫,這也是經常提的緩存命中率問題。
比如查詢用戶信息,每次都會訪問DB,如果有人惡意破壞,很可能直接對DB造成影響。

有很多種方法可以有效地解決緩存穿透問題
(1)最常見的則是採用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。

(2)另外也有一個更爲簡單粗暴的方法,如果一個查詢返回的數據爲空(不管是數據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。
通過這個直接設置的默認值存放到緩存,這樣第二次到緩衝中獲取就有值了,而不會繼續訪問數據庫,這種辦法最簡單粗暴!

2、緩存失效
如果緩存集中在一段時間內失效,DB的壓力凸顯。這個沒有完美解決辦法,但可以分析用戶行爲,儘量讓失效時間點均勻分佈。
當發生大量的緩存穿透,例如對某個失效的緩存的大併發訪問就造成了緩存雪崩。

3、緩存雪崩
緩存雪崩我們可以簡單的理解爲:由於原有緩存失效,新緩存未到期間(例如我們設置緩存時採用了相同的過期時間,在同一時刻出現大面積的緩存過期),所有原本應該訪問緩存的請求都去查詢數據庫了,而對數據庫CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機,從而形成一系列連鎖反應,造成整個系統崩潰。

緩存失效時的雪崩效應對底層系統的衝擊非常可怕!
(1)在併發量不是很大的情況下,可以考慮用加鎖或者隊列的方式保證來保證不會有大量的線程對數據庫一次性進行讀寫,從而避免失效時大量的併發請求落到底層存儲系統上。
(2)還有一個簡單方案就時講緩存失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存的過期時間的重複率就會降低,就很難引發集體失效的事件。

4、緩存預熱
緩存預熱這個應該是一個比較常見的概念,相信很多小夥伴都應該可以很容易的理解,緩存預熱就是系統上線後,將相關的緩存數據直接加載到緩存系統。
這樣就可以避免在用戶請求的時候,先查詢數據庫,然後再將數據緩存的問題!用戶直接查詢事先被預熱的緩存數據!

解決思路:
(1)直接寫個緩存刷新頁面,上線時手工操作下;
(2)數據量不大,可以在項目啓動的時候自動進行加載;利用Spring BeanPostProcessor 初始化時加載數據到緩存
(3)定時刷新緩存;

5、緩存更新&數據一致
除了緩存服務器自帶的緩存失效策略之外(Redis默認的有6中策略可供選擇,http://www.redis.cn/topics/lru-cache.html),
一般的流程,開啓事務 ——> update DB ——> update cache ——> 提交事務 ——> 返回結果
關鍵是update cache,如果重試之後還是失敗,如果要求DB和Cache強一致,緩存更新失敗後一定要拋出異常,讓整個事務回滾,避免DB和Cache數據的不一致。
我們在修改數據庫後,緩存服務器掛了無法修改緩存,這時候可以將這條數據放到數據庫中,同時啓動一個異步任務定時去檢測緩存服務器是否連接成功,一旦連接成功則從數據庫中按順序取出修改數據,依次進行緩存最新值的修改。

6、緩存降級
當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的性能時,仍然需要保證服務還是可用的,即使是有損服務。
系統可以根據一些關鍵數據進行自動降級,也可以配置開關實現人工降級。降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結算)。
在進行降級之前要對系統進行梳理,看看系統是不是可以丟卒保帥;從而梳理出哪些必須誓死保護,哪些可降級;比如可以參考日誌級別設置預案:
(1)一般:比如有些服務偶爾因爲網絡抖動或者服務正在上線而超時,可以自動降級;
(2)警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,併發送告警;
(3)錯誤:比如可用率低於90%,或者數據庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;
(4)嚴重錯誤:比如因爲特殊原因數據錯誤了,此時需要緊急人工降級。

7、無底洞問題
Facebook的工作人員反應2010年已達到3000個memcached節點,儲存數千G的緩存。
他們發現memcached的連接效率下降了,於是添加memcached節點,添加完之後,並沒有好轉。
這就是緩存無底洞問題,節點數越多性能並沒有相應的提升。
緩存無底洞產生的原因:鍵值數據庫或者緩存系統,由於通常採用hash函數將key映射到對應的實例,造成key的分佈與業務無關,但是由於數據量、訪問量的需求,
需要使用分佈式後(無論是客戶端一致性哈性、redis-cluster、codis),批量操作比如批量獲取多個key(例如redis的mget操作),通常需要從不同實例獲取key值,相比於單機批量操作只涉及到一次網絡操作,分佈式批量操作會涉及到多次網絡io。
(1) 客戶端一次批量操作會涉及多次網絡操作,也就意味着批量操作會隨着實例的增多,耗時會不斷增大。
(2) 服務端網絡連接次數變多,對實例的性能也有一定影響。
更多的機器不代表更多的性能,投入越多不一定產出越多。
隨着訪問量和數據量越來越大,分佈式往往是不可以避免的,如何高效的在分佈式緩存/存儲系統中批量獲取數據是一個難點。

8、總結
實際上在使用緩存的過程中還有很多各種各樣的問題,不可能枚舉所有場景,相對來說以上問題更更常見。
正式業務場景往往很複雜,應用場景不同,方法和解決方案也不同,具體解決方案要根據實際情況來確定!

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