缓存面试五连击(下篇)
接著上篇去聊一下,緩存的主要類型
?
緩存的類型主要分為客戶端緩存,瀏覽器緩存,CDN緩存,反向代理緩存,應用緩存等;
客戶端緩存相對于其他端的緩存而言,要簡單一些,目的就是加速各種靜態資源的訪問,想想現在的大型網站,隨便一個頁面都是一兩百個請求,每天 pv 都是億級別,如果沒有緩存,用戶體驗會急劇下降、同時服務器壓力和網絡帶寬都面臨嚴重的考驗。客戶端緩存主要分為兩種:頁面緩存和瀏覽器緩存
?
?
頁面緩存
?
頁面緩存有兩層含義: 一個是頁面自身對某些元素或全部元素進行緩存;另一層意思 是服務端將靜態頁面或動態頁面的元素進行緩存, 然后給客戶端使用。 這里的頁面緩存指 的是頁面自身的緩存或者離線應用緩存。
頁面緩存是將之前渲染的頁面保存為文件, 當用戶再次訪問時可以避開網絡連接, 從而減少負載, 提升性能和用戶體驗。
?
?瀏覽器緩存
?
瀏覽器緩存:這種形式使用很廣泛,極大地提升了用戶體驗,但有時會出現沒及時更新導致顯示“錯誤”的信息。把已經請求過的Web資源(如html頁面,圖片,js,css等)拷貝一份副本儲存在瀏覽器中,緩存會根據進來的請求保存輸出內容的副本。這種緩存帶來的好處有三點:減少網絡帶寬消耗,降低服務器壓力,減少網絡延遲、加快頁面打開速度,適合請求量大、靜態的數據請求。
當我們請求一個網頁的時候,服務器會向瀏覽器返回大量數據,但是這些數據需要全部緩存嗎?瀏覽器又是如何區分哪些數據需要進行緩存,哪些是需要實時跟源站獲取的?接下來我們就來看一下瀏覽器的緩存策略。
服務器可以在響應中返回 ETag,然后瀏覽器會在后續的請求中攜帶上這個參數來確定緩存是否需要更新。如果 ETag 值相同,說明資源未更改,服務器會返回 304響應碼,瀏覽器就知道本地緩存仍然是可以使用的。
不過需要注意的是,ETag 只有在本地緩存已過期(Expires)或者緩存模式設置為 no-cache(Cache-Control)的時候,才會被瀏覽器攜帶上與服務器端的值進行判別。
?
?
CDN緩存
CDN 是互聯網上內容分發的重要一環。無論您之前是否了解過 CDN,其實它已經在您的日常生活中發揮作用了。比如您正在淘寶挑選心儀的商品,或者在觀看本視頻,這些資源展示的背后都有 CDN 的默默支撐。
?
為什么 CDN 使用如此廣泛呢?首先大家需要知道,CDN 旨在解決的最重要的問題是什么,我們稱之為網絡延遲。舉個例子,當您輸入一個網址,敲擊回車后到網頁內容實際出現在屏幕上,中間加載耗費的這個時間,就是網絡延遲。通過網絡獲取資源總是比從本地獲取慢,無論服務器是在同一個局域網中還是位于世界的另一個角落,都是如此。這里的速度差異是 IT 行業的一個核心問題,開發者想了很多辦法試圖去彌補這個差異,CDN 就是應用最為廣泛的一個解決方案。
CDN 為解決網絡延遲提供了一整套技術方案,在使用了 CDN 之后,數據是如何被緩存的,以及緩存是如何提高數據加載速度的。
?
在未接入CDN 之前,用戶使用瀏覽器訪問服務的時候,相互交互的過程如下圖所示。
?
用戶在第一次訪問網站服務器的時候,瀏覽器會從服務器獲取所有的資源,在傳輸過程中,瀏覽器會通過一些約定好的響應頭,從而確定是否需要將這個資源保存一份到本地作為緩存。當用戶第二次訪問該網站的時候,瀏覽器就會優先從緩存中加載資源,不用向服務器請求資源,從而提高了網站的訪問速度。
?
而對于一些用戶訪問量巨大的網站而言,如果所有用戶都去服務器請求數據,服務器會很快崩潰,并且在不同網絡以及不同地區的用戶,請求服務器的速度也不一樣。為了提高這部分用戶的訪問速度,CDN 中又提出了新的網絡架構,即創建一些最接近用戶網絡的邊緣服務器,然后將文件緩存在這些邊緣服務器(節點)上,這就是 CDN 緩存。
?
?
反向代理緩存
?
最近工作中用到反向代理,我只知道有代理這個概念,并不清楚代理還有正向和反向之分,首先弄清楚什么是正向代理,什么是反向代理,然后是二者在實際使用中展示的方式是什么樣的,最后總結一下正向代理用來做什么,反向代理可以做什么。
?
正向代理( Forward Proxy ):
目前國內無法訪問google,但是我們有時說掛個代理,然后就能順利訪問,而這種代理模式就是正向代理。假如我們在香港有一臺服務器,這臺服務器是能訪問google的,而國內無法直接訪問谷歌,但是可以訪問香港的服務器。每次我們請求香港服務器,香港服務器拿到我們請求以后,再去訪問google服務器,google服務器把響應返回給香港服務器,香港服務器再把響應返回給我們。這樣我們就能順利的訪問google了。
?
正向代理是指是一個位于客戶端和原始服務器之間的服務器,為了從原始服務器取得內容, 客戶端向代理發送一個請求并指定目標(原始服務器),然后代理向原始服務器轉交請求并將獲得的內容返回給客戶端。客戶端才能使用正向代理。
?
正向代理最大的特點是
?
?
?
反向代理( Reverse Proxy ):
?
例如淘寶,每天訪問量很大,不可能只用單個服務器處理所有業務,于是出現了分布式部署。也就是通過部署多臺服務器來解決訪問人數限制的問題。
客戶端請求taobao.com,DNS服務器把域名解析到nginx服務器上(簡單的這么理解),nginx服務器接收到之后,按照一定的規則分發給了后端的業務處理服務器進行處理了。
?
反向代理是指以代理服務器來接受 Internet 上的連接請求,然后將請求轉發給內部網絡上的服務器,并將從服務器上得到的結果返回給 Internet 上請求連接的客戶端,此時代理服務器對外就表現為一個反向代理服務器
?
反向代理最大的特點是
正向代理其實是客戶端的代理,反向代理則是服務器的代理。正向代理中,服務器并不知道真正的客戶端到底是誰;而在反向代理中,客戶端也不知道真正的服務器是誰。正向代理主要是用來解決訪問限制問題;而反向代理則是提供負載均衡、安全防護等作用。
?
?
上面講到的代理緩存、反向代理緩存、CDN 緩存,都是通讀緩存。它代理了用戶的請求,也就是說用戶在訪問數據的時候,總是要通過穿透型緩存。
今天我們來介紹一下緩存架構的常用實現方式,常見的緩存架構主要有兩種:
?
穿透型緩存是將緩存與后端數據庫的交互細節對應用層服務隱藏
應用層服務所有的讀寫請求均請求緩存,讀請求?miss?后,緩存向后端數據服務器請求數據,先更新緩存后返回
而寫請求也是同樣的,先寫入緩存服務器,后同步給后端服務器
?
在旁路緩存模式中,如果數據未緩存,應用便從后端存儲中獲取數據,并將數據放入緩存中以備后續讀取。
這種模式的優勢在于它不需要開發人員向緩存服務器中部署任何代碼。相反,旁路模式讓開發人員和應用代碼負責管理緩存。但緩存控制這個優勢,也需要和緩存管理成本一起考慮。
大部分業務場景是“一寫多讀”的場景,在這樣的場景下,旁路型緩存是非常適用的
讀請求
上圖展示了旁路型緩存的讀?miss?情況的處理:
?
寫請求
對于寫請求,這個模式要求所有的數據更新都需要刪除緩存中對應的數據,官方建議旁路型緩存的設計原則是先操作后端數據庫后操作緩存
?
在旁路緩存模式中,在應用層的控制力更大。而在穿透型緩存中,代碼部署到緩存服務器,由緩存負責控制和后端存儲之間的讀寫交互。
?
在這里插入講解一下各種介質數據訪問的延遲,以便對數據的存儲、緩存的特性以及數據的訪問延遲有一個感性的認識。??
?
作為一名優秀的架構師,更應該深入理解各種介質解數據訪問的延遲,以便對計算機的層次存儲結構,緩存特性以及數據延遲有感性的認識,有利于我們更好的利用程序的局部性原理,編寫出高效的代碼,所以話不多說,先附上一張圖說明情況。
存儲器是分層次的,離CPU越近的存儲器,速度越快,每個字節的成本越高,同時容量也因此越小。寄存器速度最快,離CPU最近,成本最高,所以以個數容量有限,其次是高速緩存(緩存也是分級,有L1,L2等緩存),再次是主存(普通內存),再次是本地磁盤。
在計算機中, CPU所需要的數據全部都來自于內存, 不管是在內存本身的數據還是在磁盤上的數據還是存在網絡上的數據, 最終終究是會讀取到內存中去, 然后CPU才能夠得到數據并作出相應的反應。而在CPU和內存之間就存在者另一種一種物理硬件就是緩存,緩存中往往存著CPU正在執行的一些指令,這樣就會減少CPU對內存訪問的次數,從而加快CPU的執行效率。在這里所講的緩存是計算機設備的緩存。
?
?
雖然使用緩存給系統帶來了一定的質的提升,但同時也帶來了一些需要注意的問題。
使用Redis時經常遇到的幾個問題。緩存雪崩、緩存穿透、緩存預熱、緩存更新。
首先我們使用Redis的邏輯是這樣的
?
?
?
對于系統 A,假設每天高峰期每秒 5000 個請求,本來緩存在高峰期可以扛住每秒 4000 個請求,但是緩存機器意外發生了全盤宕機。緩存掛了,此時 1 秒 5000 個請求全部落數據庫,數據庫必然扛不住,它會報一下警,然后就掛了。此時,如果沒有采用什么特別的方案來處理這個故障,DBA 很著急,重啟數據庫,但是數據庫立馬又被新的流量給打死了。 解決辦法:
?
?
用戶發送一個請求,系統 A 收到請求后,先查本地緩存,如果沒查到再查 redis。如果 本地緩存 和 redis 都沒有,再查數據庫,將數據庫中的結果,寫入 本地緩存 和 redis 中。
限流組件,可以設置每秒的請求,有多少能通過組件,剩余的未通過的請求,怎么辦?走降級!可以返回一些默認的值,或者友情提示,或者空白的值。
這種架構的好處是:
?
2、緩存穿透
對于系統A,假設一秒 5000 個請求,結果其中 4000 個請求是黑客發出的惡意攻擊。
黑客發出的那 4000 個攻擊,緩存中查不到,每次你去數據庫里查,也查不到。
舉個栗子。數據庫 id 是從 1 開始的,結果黑客發過來的請求 id 全部都是負數。這樣的話,緩存中不會有,請求每次都“視緩存于無物”,直接查詢數據庫。這種惡意攻擊場景的緩存穿透就會直接把數據庫給打死。
?
解決方式很簡單,每次系統 A 從數據庫中只要沒查到,就寫一個空值到緩存里去。然后設置一個過期時間,這樣的話,下次有相同的 key 來訪問的時候,在緩存失效之前,都可以直接從緩存中取數據。
?
?
3、緩存預熱
是一種機制, 就是系統上線后,提前將相關的緩存數據直接加載到緩存系統。避免在用戶請求的時候,先查詢數據庫,然后再將數據緩存的問題。
4、緩存更新
怎么樣保證緩存中的key是實時有效的,以及及時的更新數據資源
解決辦法:
1)緩存服務器自帶的緩存失效策略
2)定時去清理過期的緩存;當用戶請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統得到新數據并更新緩存。
?
?
?
分布式對象緩存
下面看一下分布式對象緩存,如下圖所示。
?
為什么要使用分布式緩存
高并發環境下,例如典型的淘寶雙11秒殺,幾分鐘內上億的用戶涌入淘寶,這個時候如果訪問不加攔截,讓大量的讀寫請求涌向數據庫,由于磁盤的處理速度與內存顯然不在一個量級,服務器馬上就要宕機。從減輕數據庫的壓力和提高系統響應速度兩個角度來考慮,都會在數據庫之前加一層緩存,訪問壓力越大的,在緩存之前就開始CDN攔截圖片等訪問請求。
?
那要是我們的緩存節點掛了,不可用了,那豈不是又回到了原點,請求都會打到我們的數據庫中的。所以,我們在使用緩存一定要搭建高可用緩存,避免上面的單點緩存架構。今天,我們就來學習該怎么做緩存的高可用方案即搭建分布式緩存的高可用方案。
?
依據經驗來說,對于分布式緩存高可用方案目前一般采用應用端、中間代理層以及服務端這三大方案。
?
接下來我們就來分別看一下這三種方案
應用端方案
在應用端也就是代碼層面上,我們就需要自己管理緩存的讀和寫,也就是通過寫代碼方式來進行分布式緩存的寫入和讀取,主要是下面這兩模塊:
下面我們來看看該怎么進行設計,其實這種設計思路不一定局限在緩存上,我們大部分的底層開發都能用上,希望大家好好掌握
緩存數據如何分片
我們知道單節點的緩存因受到各種原因如本身機器內存、網絡帶寬等,從而不能承受更高的并發,所以我們需要將數據進行分片存儲,即將數據通過分片算法打散到各個緩存節點中。其實這塊大家有沒有注意到和我們前面的分庫分表很類似,所以大部分架構思想都是相通的。
現在我們的數據就在各個緩存節點都有一部分,即使部分故障,也是不影響我們整個業務的。那這個時候,你可能在想,既然數據需要被均勻分散到各個節點,那我該怎么來寫這個分片算法呢?別急,我們下面就來看怎么寫這個分片算法。
數據分片算法
一般做數據分片算法的有兩種,大家應該都清楚吧,前面分庫分表就有用到的
- Hash分片算法
- 一致性Hash分片算法
Hash分片算法
Hash分片算法就是我們拿到緩存的key,然后對其做hash運算,最后將hash運算的結果對緩存總節點數取余,得到的數字則為具體的分片節點。比如,現在我們緩存節點一共有 3 個,當我們寫入數據的時候,將key進行hash運算hash(key),然后將結果對3取余就行了,如下圖所示:
?
這種分片算法優點就是開發簡單且容易理解,缺點就是當我們的緩存總節點數改變的時候,就會導致數據不均勻,則會造成大量緩存失效不可用的情況。但是這種算法我們開發中也是會使用的,比如我們的業務對于緩存的命中率不是那么太在意的,就可以使用這種hash分片算法。
一致性Hash分片算法
上面簡單的Hash分片算法對緩存命中率要求較高的業務會有一定影響,所以一致性Hash分片算法就出來了,它很好的解決了因緩存節點的增加或減少帶來的緩存命中率下降的問題。那我們就來看看它是怎么做的。
基于上面的問題,提出了hash環的概念。hash環的過程有兩次hash
(1) 把所有的機器編號hash到這個環上
(2) 把key也hash到這個環上,然后在這個環上進行匹配,看這個key和哪臺機器匹配
?
具體過程是這樣: 假定有一個hash函數,其值空間為(0 ~ 2^32-1)。也就是說,其hash值是個32位無整型數字,這些數字組成一個環。首先對機器進行hash(比如根據機器ip),算出每臺機器在這個環上的位置; z再對key進行hash,算出該key在環上的位置,然后從這個位置往前走,遇到的第一臺機器就是該key對應的機器,就把該(key, value)存儲到該機器上,如下圖所示。
?
首先計算出每臺cache服務器在環上的位置(圖中淺藍色的大圓圈),然后每來一個key計算出value填到環上的位置(圖中橙色的小圓圈),然后順時針走,遇到的第一個機器,就是要存儲的機器
這里的關鍵點是:當機器數N變化時,其他機器在環上的位置并不會發生改變。這樣只有增加/減少的那臺機器附近的數據會失效,其他機器上的數據還是有效的。
數據傾斜問題
當機器不很多時,很可能出現幾臺機器在環上面貼的很近,分布很不均勻。這將會導致大部門數據集中在某幾臺機器上。
為了解決這個問題,可以引入"虛擬機器"的概念,也就是說,一臺機器需要在環上映射出多個位置。比如我們用機器的ip來hash,那么我們可以在ip后面加幾個編號,形如ip_1, ip_2, ip_3... 這樣就實現了一臺物理機器映射出了多個虛擬機器的編號。
數據首先映射到"虛擬機器"上,再從"虛擬機器"映射到物理機器上。因為虛擬機器可以很多,在環上均勻分布,從而保證數據相對均勻地分布在物理機器上。
?
?
?zk的引入
上面我們提到了服務器的機器數N的變化,那么如何通知到客戶端呢
一種笨方法就是手動,當機器數N變化,重新配置客戶端,重啟客戶端。
另外一種,引入zk,服務器的節點列表注冊到zk上面,客戶端監聽zk。發現節點數發生變化,自動更新自己的配置。
當然不用zk用一個其他的中心節點也可以,只要能實現這種更改的通知即可(也即分布式服務協調)
?
中間代理層方案
上面的應用端方案基本能解決我們絕大部分問題了,現在主要是像有些公司技術語言比較多的話,這種就得每種語言都得開發一套,比如我們公司有Java PHP 還有.net之類的,那么這個時候就需要中間代理層來最好不過了,不需要業務方進行考慮這些復雜情況,直接連接代理層就行了
代理層自己管理緩存節點高可用,通過某種協議,如redis協議,來和各種語言業務端連接。業界也有很多中間代理層方案,比如Twitter 的Twemproxy,豌豆莢的Codis。基本架構如下
如圖所示,中間層代理方案即所有緩存讀寫的操作都直接通過代理層完成,代理層自己完成上面應用端所有的操作。
服務端方案
服務端方案主要是緩存服務自己管理的,對于開發人員不用自己寫代碼管理,也不用引入中間層,就是需要相關運維配置支持,比如redis的sentinel模式就是用來解決redis部署時高可用問題,它可以在主節點掛了以后自動將從節點提升為主節點,保證整體集群的可用。
總結,今天我們講到了在使用緩存的時候為了避免單節點所帶來的各種問題,所以我們需要搭建高可用緩存架構,共講到了三種方案,應用端、中間代理層以及服務端方案,大家可以根據公司的資源情況來選擇合適的方案。
總結
以上是生活随笔為你收集整理的缓存面试五连击(下篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring boot 搭建个人博客系统
- 下一篇: 为ARM处理器实现Machine For