1. 程式人生 > 實用技巧 >「面試」快取知識點大總結

「面試」快取知識點大總結

週末一刻

這周過的比較快,事情也比較多,依然無法阻擋我每週一浪的節奏。

先去了“傑論”的奶茶店,好不好喝不做評價,主要是捧個場,就感覺你們每次在文章下面點個贊一樣。

在這裡插入圖片描述

這周還去了所謂網紅橋,被網友們稱為成都橋樑界的"新晉扛把子"。看看長撒子樣子

五岔子大橋

這個大橋聽說參考了"無限之環"—莫比烏斯杯的概念,將可能在四維空間中才存在的抽象涉及到三維空間,就彷彿數學符號"∞",以此表達高新區無限可能性和開放發展的廣闊胸懷,當然也給與了不少的工作機會

吃完串串,這個串串感覺真的巴適。吃完約幾個朋友漫步於橋上,聽著河水流動的呲呲聲,呼吸著溼潤的空氣是不是很xui服,

葉婆婆
沒想到吃完了去抽個獎,啊哈,中了100的代金卷,沒想我旁邊的菇涼比我還激動,當然我確實沒想到我會抽個100的代金卷,畢竟一直都是:“感謝抽獎”。下次請你吃?

代金卷


生活著不就這樣嘛,吃吃喝喝,逛逛,別天天想著買房買車,給自己壓力,有個大大的夢想,咦,分治思想嘛,化成一個個小目標慢慢實現勒

在這裡插入圖片描述

好了,還是學點東西,讓我有點東西。

大綱

使用好快取就彷彿擁有了無比鋒利的寶劍,對於效能的提升是立竿見影的。但是如果使用的不好就會嚴重的影響系統的執行,那麼今天我們看看快取到底是什麼以及快取的應用模式

1 快取的本質

網上有各種對快取的定義和解釋,我認為快取是為了節約對原始資源重複獲取的開銷

什麼是資源?

首先如果這個資源是經常變化的,你覺得快取了有什麼意義。顯然不可行,所以,我們對資源的操作需要時是冪等切安全的。舉個例子,比如銀行轉賬,改變了目標狀態,自然就不能快取。

其次,快取的資料是"重複"獲取的。意味著將經常使用的資料快取下來,當下一次訪問的時候直接返回即可,這樣節約了通過原始流程訪問資料的時間。比如資料庫中的連線池,執行緒池,就是為了避免頻繁的建立連結,銷燬連結而出。

如何衡量這個快取的效果?

在資料獲取的過程中,通過快取獲得資料的次數,除以總的次數所得到的結果為緩衝命中率

加了一層快取就一定更快?

非也。首先這個快是相對而言的,因為在不同的系統中對於同一物件可能扮演的角色不同。比如CPU的多級快取記憶體,即記憶體的快取。而記憶體對於CPU儲存而言又比較慢,但是對於磁碟卻快了很多,所以它可以用作磁碟的快取介質

能夠舉一個常見的例子來闡述快取的應用?

如果看了我之前寫的面試文章,應該對這個面試題非常的熟悉了。“當敲入URL會經歷什麼?”,在那篇文章中,寫了一萬多字來闡述這個問題,意味著如果面試官問你這個問題,你可以講什麼且可以從一個點出發將的比較深。回到這裡,如果我讓你以快取的角度來闡述這個問題,你將如何去回答?

首先在位址列輸入域名,會查詢瀏覽器內部"域名-IP"快取,如果之前通過這個瀏覽器訪問過這個域名,那麼就有一張對映表。

如果沒有,則會查詢作業系統是否存在這個快取,如果是mac電腦則/etc/hosts就可以定義域名到IP的關係。

如果還是沒有則會查詢域名伺服器DNS得到對應的IP和可快取的時間。如果需要使用命令來測試,這裡可以同dig命令檢視這個詳細過程。

當請求到達了服務端,通常會有反向代理,在反向代理中會存在快取配置,對於一個MVC架構的應用而言,MVC的各個層都有可以應用快取模式

Controller層有攔截過濾器,可以通過配置快取來過濾我們需要的請求,這樣過濾器直接返回快取結果而不執行後面的邏輯

對於Model層,在DAO上的Service暴露API應用快取也是非常常見的操作

對於View層,頁面標籤居多,不會每次都去渲染,而是通過快取標籤的方式節省渲染操作

這一切操作完事後就會返回給瀏覽器,瀏覽器會載入頁面中大量的資源,這都是通過讀取瀏覽器的快取來避免HTTP請求的開銷。

看吧,我們在不斷衝浪的同時,下面有著這麼精彩的故事,也說明了快取的重要性了吧

3 快取應用模式

通過不同的特性和優缺點,將快取分了幾種模式,我們看看這幾種模式的區別以及優缺點

Cache-Aside

資料庫的異常情形

  • 如果資料庫讀取異常直接返回失敗,不會存在資料不一致的情況
  • 如果資料讀取成功了,但是快取的寫入失敗,那麼下次訪問統一資料的時候繼續嘗試寫入,也沒有資料不一致的情況

所以這兩種情況是比較安全的

資料更新的策略,到底是先更新資料庫還是先讓快取失效

假設我們讓快取顯示小,意味著在資料庫更新成功之前,如果另外的請求訪問快取,此時快取已經失效,那麼就會按照原來的訪問策略,從資料庫中使用這個已經舊的資料去更新快取的資料,這樣就導致過期的資料會長期存放於快取中,最終導致資料不一致。

為了加深大家對這個概念的理解,畫了下面這幅圖

Cache-Aside

第二需要注意的是:資料庫更新以後,需要讓快取失效而不是更新快取為最新的值

為什麼呢?如果兩個幾乎同時發出的請求分別更新資料庫中的值A和B,如果結果是B的更新晚於A,那麼資料中的最終值為B。但是,如果資料庫更新後去更新快取,而不是讓快取失效,那麼快取中的資料就有可能是A,而不是B。因為資料庫雖然是"更新為A"在"更新為B"之前發生,但如果不做特殊的跨儲存系統的事務控制,快取的更新順序就未必遵從"A先於B"這個規則,就會導致快取中的資料是一個長期錯誤的值A。

同樣畫個圖,讓大家理解更深刻

先讓快取失效

4 快取常出現的問題

上面大多數時候都在談論快取帶來的好處,現在該說說其弊端了

  • 快取穿透

當在同一時間有大量的資料訪問,經過了快取屏障但是並沒有起到應有的保護作用。舉個例子,需要在資料庫對一個key進行查詢,如果資料庫中沒有這個資料,那麼快取也沒有這個資料,每次請求都會去資料庫查,快取再次根本沒有作用

怎麼解決這個問題勒?

我們可以在快取中對這個key存放一個空結果,畢竟沒有結果也是結果嘛

還有一種比較嚴重的快取穿透後果。在一般的快取策略下,通常需要先發生一次快取命中失敗,然後從資料庫得到結果,再填到記憶體快取中。但是,如果這個資料庫的查詢太慢,此時大量的資料接踵而至,全部穿透快取落在資料庫上, 此時對於最初的請求所引發的快取可能還沒來得及混村,資料庫就直接掛掉了,這種問題有那麼解決方案呢

還有其他的解決方案?

使用布隆過濾器。主要用來判斷一個元素是否在一個集合中,這個演算法由一個二進位制資料和一個hash演算法組成,其基本的思路是這樣的:

我們把集合中的每一個值按照提供的 Hash 演算法算出對應的 Hash 值,然後將 Hash 值對陣列長度取模後得到需要計入陣列的索引值,並且將陣列這個位置的值從 0 改成 1。在判斷一個元素是否存在於這個集合中時,你只需要將這個元素按照相同的演算法計算出索引值,如果這個位置的值為 1 就認為這個元素在集合中,否則則認為不在集合中。 畫個圖看看就更清楚了

**布隆過濾器**

ABC組成一個集合,元素D計算出來對應的數值為1,所以D也存在於集合。但是F在陣列值為0,所以不在集合中了。

如何通過布隆過濾器解決快取穿透?

我們以儲存使用者為例子,初始化一個大陣列,長度為10億的資料,選擇一個Hash演算法,計算使用者的ID的Hash值並對映到這個大陣列中,對映的位置為1,其他設定為0

一旦存在新註冊的使用者,當我們需要查詢一個使用者的資訊的時候,首先查詢的ID在布隆過濾器中是否存在,不存在則直接返回空,不繼續查詢資料庫和快取,這樣不就大大的減少異常查詢帶來的快取穿透

解決快取穿透

過濾器具有非常高的效能,無論是寫入還是讀取時間複雜度都是O(1)。空間上也是有著不錯的又是,假設20億 的資料需要2000000000/8/1024/1024 = 238M 控制,而如果使用陣列來村粗,每個使用者ID佔4個位元組,儲存20億 需要2000000000 * 4 / 1024 / 1024 =7600M 的空間,是布隆過濾器的 32 倍

布隆過濾器有哪些缺陷?

  • 判斷元素是否在集合中有一定的錯誤機率

這個問題是hash演算法的問題,Hash演算法難免有一定的碰撞機率,所謂碰撞,即不同的輸入值得到相同的Hash結果

  • 不支援刪除元素

舉個例子,假設兩個元素A和B都是集合中的元素,具有相同的Hash值,會對映到陣列相同的位置。此時如果刪除A,陣列中對應的位置從1變為-,那麼在判斷B的時候發現B不在集合中了,得到了錯誤的結論

如何解決這個問題

讓陣列中不在只有0和1兩個數值,直接儲存一個計數。假設AB同時命中陣列的索引,此時這個值為2,如果A刪除,將這個值改為1。當然這樣就會增加空間的消耗,所以使用過濾器根據業務需求而定。

流量控制

限制對於同一資料的訪問,必須等到前一個完成以後才能進行下一個,即如果快取失效而引發的資料庫查詢正在進行,那麼請他的請求只好乖乖的等著,這種方法通用性還可以,但是這個等待機制會比較影響使用者的體驗

快取預熱

在大量請求到來之前先預熱一波,簡單高效,侷限性在於需要提前知道那些資料可能引發快取穿透的問題

  • 快取雪崩

在一定時間段出現大量的請求訪問失效,直接造成後方的系統壓力過大,引起系統過載,宕機,這就是快取雪崩。

通常採取限流和預熱兩種方式進行避免。限流可以保障請求大量落到資料庫的時候,系統只接納能夠承載的數量。對於預熱則主動先王記憶體中載入一定的熱點資料,請求到來的時候快取不是空的,這樣具有一定的保護能力。

快取資料存在過期時間,如果快取載入時間比較集中,那麼此時大量的快取同時國企,對這些資料的請求將全部落在後面的資料庫從而導致系統崩潰,如何避免?

這裡主要避免快取集中寫入時間,如果無法避免則使用一個範圍的隨機數均勻分散過期時間,從而打散系統造成的壓力

  • LRU致命缺陷

LRU即Least Recently Used,最少最近使用演算法,原理是維護一個限定最大容量的佇列,佇列頭部總是放置最近訪問的元素,超過容量限制的時候從隊尾淘汰元素。

通過一個圖看看LRU工作原理

LRU

這看起來是 個不錯的方案,需要從快取淘汰資料的時候總是從尾部淘汰一個不常用的,但是如果有使用者有意無意的訪問一些錯誤資訊,此時就會破壞整個LRU佇列中最近訪問資料的真實性。

如果一不小心咱們快取中存放不少冷門資料,熱門活動開始後,大量的請求到來,全部穿透快取導致資料庫壓力倍增,網站響應時間瞬間到告警線上。

怎麼解決這個問題?

這裡基於LRU的改進方案很多,簡單說下LRU-K,即主快取佇列排的是"第K次訪問的元素",也就是如果訪問的次數小於k,則將其放在另一個"低階"的佇列中維護,這樣保證在一定的訪問下限才能被送到LRU主佇列中

5 小結

其中總結了我覺得比較高頻的關於快取的題,希望對大家有所幫助,下一週大大要過來,意味著9點下班可能成為日常,是高興呢還是高興?求個,麼麼噠