大型網站多級緩存的分層架構

来源:https://www.cnblogs.com/winner192/archive/2019/10/18/11699614.html
-Advertisement-
Play Games

在互聯網高速發展的今天,緩存技術被廣泛地應用。無論業內還是業外,只要是提到性能問題,大家都會脫口而出“用緩存解決”。 這種說法帶有片面性,甚至是一知半解,但是作為專業人士的我們,需要對緩存有更深、更廣的瞭解。 緩存技術存在於應用場景的方方面面。從瀏覽器請求,到反向代理伺服器,從進程內緩存到分散式緩存 ...


在互聯網高速發展的今天,緩存技術被廣泛地應用。無論業內還是業外,只要是提到性能問題,大家都會脫口而出“用緩存解決”。

這種說法帶有片面性,甚至是一知半解,但是作為專業人士的我們,需要對緩存有更深、更廣的瞭解。

緩存技術存在於應用場景的方方面面。從瀏覽器請求,到反向代理伺服器,從進程內緩存到分散式緩存。其中緩存策略,演算法也是層出不窮,今天就帶大家走進緩存。

正文

緩存對於每個開發者來說是相當熟悉了,為了提高程式的性能我們會去加緩存,但是在什麼地方加緩存,如何加緩存呢?

假設一個網站,需要提高性能,緩存可以放在瀏覽器,可以放在反向代理伺服器,還可以放在應用程式進程內,同時可以放在分散式緩存系統中。

 

 

從用戶請求數據到數據返回,數據經過了瀏覽器,CDN,代理伺服器,應用伺服器,以及資料庫各個環節。每個環節都可以運用緩存技術。

從瀏覽器/客戶端開始請求數據,通過 HTTP 配合 CDN 獲取數據的變更情況,到達代理伺服器(Nginx)可以通過反向代理獲取靜態資源。

再往下來到應用伺服器可以通過進程內(堆內)緩存,分散式緩存等遞進的方式獲取數據。如果以上所有緩存都沒有命中數據,才會回源到資料庫。

緩存的請求順序是:用戶請求 → HTTP 緩存 → CDN 緩存 → 代理伺服器緩存 → 進程內緩存 → 分散式緩存 → 資料庫。

看來在技術的架構每個環節都可以加入緩存,看看每個環節是如何應用緩存技術的。

1. HTTP緩存

當用戶通過瀏覽器請求伺服器的時候,會發起 HTTP 請求,如果對每次 HTTP 請求進行緩存,那麼可以減少應用伺服器的壓力。

當第一次請求的時候,瀏覽器本地緩存庫沒有緩存數據,會從伺服器取數據,並且放到瀏覽器的緩存庫中,下次再進行請求的時候會根據緩存的策略來讀取本地或者服務的信息。

 

 

一般信息的傳遞通過 HTTP 請求頭 Header 來傳遞。目前比較常見的緩存方式有兩種,分別是:

  • 強制緩存

  • 對比緩存

1.1. 強制緩存

當瀏覽器本地緩存庫保存了緩存信息,在緩存數據未失效的情況下,可以直接使用緩存數據。否則就需要重新獲取數據。

這種緩存機制看上去比較直接,那麼如何判斷緩存數據是否失效呢?這裡需要關註 HTTP Header 中的兩個欄位 Expires 和 Cache-Control。

Expires 為服務端返回的過期時間,客戶端第一次請求伺服器,伺服器會返回資源的過期時間。如果客戶端再次請求伺服器,會把請求時間與過期時間做比較。

如果請求時間小於過期時間,那麼說明緩存沒有過期,則可以直接使用本地緩存庫的信息。

反之,說明數據已經過期,必須從伺服器重新獲取信息,獲取完畢又會更新最新的過期時間。

這種方式在 HTTP 1.0 用的比較多,到了 HTTP 1.1 會使用 Cache-Control 替代。

Cache-Control 中有個 max-age 屬性,單位是秒,用來表示緩存內容在客戶端的過期時間。

例如:max-age 是 60 秒,當前緩存沒有數據,客戶端第一次請求完後,將數據放入本地緩存。

那麼在 60 秒以內客戶端再發送請求,都不會請求應用伺服器,而是從本地緩存中直接返回數據。如果兩次請求相隔時間超過了 60 秒,那麼就需要通過伺服器獲取數據。

1.2. 對比緩存

需要對比前後兩次的緩存標誌來判斷是否使用緩存。瀏覽器第一次請求時,伺服器會將緩存標識與數據一起返回,瀏覽器將二者備份至本地緩存庫中。瀏覽器再次請求時,將備份的緩存標識發送給伺服器。

伺服器根據緩存標識進行判斷,如果判斷數據沒有發生變化,把判斷成功的 304 狀態碼發給瀏覽器。

這時瀏覽器就可以使用緩存的數據來。伺服器返回的就只是 Header,不包含 Body。

下麵介紹兩種標識規則:

1.2.1. Last-Modified/If-Modified-Since 規則

在客戶端第一次請求的時候,伺服器會返回資源最後的修改時間,記作 Last-Modified。客戶端將這個欄位連同資源緩存起來。

Last-Modified 被保存以後,在下次請求時會以 Last-Modified-Since 欄位被髮送。

 

當客戶端再次請求伺服器時,會把 Last-Modified 連同請求的資源一起發給伺服器,這時 Last-Modified 會被命名為 If-Modified-Since,存放的內容都是一樣的。

伺服器收到請求,會把 If-Modified-Since 欄位與伺服器上保存的 Last-Modified 欄位作比較:

  • 若伺服器上的 Last-Modified 最後修改時間大於請求的 If-Modified-Since,說明資源被改動過,就會把資源(包括 Header+Body)重新返回給瀏覽器,同時返回狀態碼 200。

  • 若資源的最後修改時間小於或等於 If-Modified-Since,說明資源沒有改動過,只會返回 Header,並且返回狀態碼 304。瀏覽器接受到這個消息就可以使用本地緩存庫的數據。

註意:Last-Modified 和 If-Modified-Since 指的是同一個值,只是在客戶端和伺服器端的叫法不同。

1.2.2. ETag / If-None-Match 規則

客戶端第一次請求的時候,伺服器會給每個資源生成一個 ETag 標記。這個 ETag 是根據每個資源生成的唯一 Hash 串,資源如何發生變化 ETag 隨之更改,之後將這個 ETag 返回給客戶端,客戶端把請求的資源和 ETag 都緩存到本地。

ETag 被保存以後,在下次請求時會當作 If-None-Match 欄位被髮送出去。

 

在瀏覽器第二次請求伺服器相同資源時,會把資源對應的 ETag 一併發送給伺服器。在請求時 ETag 轉化成 If-None-Match,但其內容不變。

伺服器收到請求後,會把 If-None-Match 與伺服器上資源的 ETag 進行比較:

  • 如果不一致,說明資源被改動過,則返回資源(Header+Body),返回狀態碼 200。

  • 如果一致,說明資源沒有被改過,則返回 Header,返回狀態碼 304。瀏覽器接受到這個消息就可以使用本地緩存庫的數據。

註意:ETag 和 If-None-Match 指的是同一個值,只是在客戶端和伺服器端的叫法不同。

2. CDN 緩存

HTTP 緩存主要是對靜態數據進行緩存,把從伺服器拿到的數據緩存到客戶端/瀏覽器。

如果在客戶端和伺服器之間再加上一層 CDN,可以讓 CDN 為應用伺服器提供緩存,如果在 CDN 上緩存,就不用再請求應用伺服器了。並且 HTTP 緩存提到的兩種策略同樣可以在 CDN 伺服器執行。

CDN 的全稱是 Content Delivery Network,即內容分髮網絡。

讓我們來看看它是如何工作的吧:

  • 客戶端發送 URL 給 DNS 伺服器。

  • DNS 通過功能變數名稱解析,把請求指向 CDN 網路中的 DNS 負載均衡器。

  • DNS 負載均衡器將最近 CDN 節點的 IP 告訴 DNS,DNS 告之客戶端最新 CDN 節點的 IP。

  • 客戶端請求最近的 CDN 節點。

  • CDN 節點從應用伺服器獲取資源返回給客戶端,同時將靜態信息緩存。註意:客戶端下次互動的對象就是 CDN 緩存了,CDN 可以和應用伺服器同步緩存信息。

CDN 接受客戶端的請求,它就是離客戶端最近的伺服器,它後面會鏈接多台伺服器,起到了緩存和負載均衡的作用。

3. 負載均衡緩存

說完客戶端(HTTP)緩存和 CDN 緩存,我們離應用服務越來越近了,在到達應用服務之前,請求還要經過負載均衡器。

雖說它的主要工作是對應用伺服器進行負載均衡,但是它也可以作緩存。可以把一些修改頻率不高的數據緩存在這裡,例如:用戶信息,配置信息。通過服務定期刷新這個緩存就行了。

 

 

以 Nginx 為例,我們看看它是如何工作的:

  • 用戶請求在達到應用伺服器之前,會先訪問 Nginx 負載均衡器,如果發現有緩存信息,直接返回給用戶。

  • 如果沒有發現緩存信息,Nginx 回源到應用伺服器獲取信息。

  • 另外,有一個緩存更新服務,定期把應用伺服器中相對穩定的信息更新到 Nginx 本地緩存中。

4. 進程內緩存

通過了客戶端,CDN,負載均衡器,我們終於來到了應用伺服器。應用伺服器上部署著一個個應用,這些應用以進程的方式運行著,那麼在進程中的緩存是怎樣的呢?

進程內緩存又叫托管堆緩存,以APC為例,同時會受到托管堆回收演算法的影響。

由於其運行在記憶體中,對數據的響應速度很快,通常我們會把熱點數據放在這裡。

在進程內緩存沒有命中的時候,我們會去搜索進程外的緩存或者分散式緩存。這種緩存的好處是沒有序列化和反序列化,是最快的緩存。缺點是緩存的空間不能太大,對垃圾回收器的性能有影響。

這裡我們需要關註幾個緩存的回收策略,具體的實現架構的回收策略會有所不同,但大致的思路都是一致的:

  • FIFO(First In First Out):先進先出演算法,最先放入緩存的數據最先被移除。

  • LRU(Least Recently Used):最近最少使用演算法,把最久沒有使用過的數據移除緩存。

  • LFU(Least Frequently Used):最不常用演算法,在一段時間內使用頻率最小的數據被移除緩存。

在分散式架構的今天,多應用中如果採用進程內緩存會存在數據一致性的問題。

這裡推薦兩個方案:

  • 消息隊列修改方案

  • Timer 修改方案

4.1. 消息隊列修改方案

應用在修改完自身緩存數據和資料庫數據之後,給消息隊列發送數據變化通知,其他應用訂閱了消息通知,在收到通知的時候修改緩存數據。

 

圖片

 

4.2. Timer 修改方案

為了避免耦合,降低複雜性,對“實時一致性”不敏感的情況下。每個應用都會啟動一個 Timer,定時從資料庫拉取最新的數據,更新緩存。

不過在有的應用更新資料庫後,其他節點通過 Timer 獲取數據之間,會讀到臟數據。這裡需要控制好 Timer 的頻率,以及應用與對實時性要求不高的場景。

 

圖片

 

進程內緩存有哪些使用場景呢?

  • 場景一:只讀數據,可以考慮在進程啟動時載入到記憶體。當然,把數據載入到類似 Redis 這樣的進程外緩存服務也能解決這類問題。

  • 場景二:高併發,可以考慮使用進程內緩存,例如:秒殺。

5. 分散式緩存

說完進程內緩存,自然就過度到進程外緩存了。與進程內緩存不同,進程外緩存在應用運行的進程之外,它擁有更大的緩存容量,並且可以部署到不同的物理節點,通常會用分散式緩存的方式實現。

分散式緩存是與應用分離的緩存服務,最大的特點是,自身是一個獨立的應用/服務,與本地應用隔離,多個應用可直接共用一個或者多個緩存應用/服務。

 

圖片

 

既然是分散式緩存,緩存的數據會分佈到不同的緩存節點上,每個緩存節點緩存的數據大小通常也是有限制的。

數據被緩存到不同的節點,為了能方便的訪問這些節點,需要引入緩存代理,類似 Twemproxy。他會幫助請求找到對應的緩存節點。

同時如果緩存節點增加了,這個代理也會只能識別並且把新的緩存數據分片到新的節點,做橫向的擴展。

為了提高緩存的可用性,會在原有的緩存節點上加入 Master/Slave 的設計。當緩存數據寫入 Master 節點的時候,會同時同步一份到 Slave 節點。

一旦 Master 節點失效,可以通過代理直接切換到 Slave 節點,這時 Slave 節點就變成了 Master 節點,保證緩存的正常工作。

每個緩存節點還會提供緩存過期的機制,並且會把緩存內容定期以快照的方式保存到文件上,方便緩存崩潰之後啟動預熱載入。

5.1. 高性能

當緩存做成分散式的時候,數據會根據一定的規律分配到每個緩存應用/服務上。

如果我們把這些緩存應用/服務叫做緩存節點,每個節點一般都可以緩存一定容量的數據,例如:Redis 一個節點可以緩存 2G 的數據。

如果需要緩存的數據量比較大就需要擴展多個緩存節點來實現,這麼多的緩存節點,客戶端的請求不知道訪問哪個節點怎麼辦?緩存的數據又如何放到這些節點上?

緩存代理服務已經幫我們解決這些問題了,例如:Twemproxy 不但可以幫助緩存路由,同時可以管理緩存節點。

這裡有介紹三種緩存數據分片的演算法,有了這些演算法緩存代理就可以方便的找到分片的數據了。

5.1.1. 哈希演算法

Hash 表是最常見的數據結構,實現方式是,對數據記錄的關鍵值進行 Hash,然後再對需要分片的緩存節點個數進行取模得到的餘數進行數據分配。

例如:有三條記錄數據分別是 R1,R2,R3。他們的 ID 分別是 01,02,03,假設對這三個記錄的 ID 作為關鍵值進行 Hash 演算法之後的結果依舊是 01,02,03。

我們想把這三條數據放到三個緩存節點中,可以把這個結果分別對 3 這個數字取模得到餘數,這個餘數就是這三條記錄分別放置的緩存節點。

 

 

 

Hash 演算法是某種程度上的平均放置,策略比較簡單,如果要增加緩存節點,對已經存在的數據會有較大的變動。

5.1.2. 一致性哈希演算法

一致性 Hash 是將數據按照特征值映射到一個首尾相接的 Hash 環上,同時也將緩存節點映射到這個環上。

如果要緩存數據,通過數據的關鍵值(Key)在環上找到自己存放的位置。這些數據按照自身的 ID 取 Hash 之後得到的值按照順序在環上排列。

 

 

如果這個時候要插入一條新的數據其 ID 是 115,那麼就應該插入到如下圖的位置。

 

 

同理如果要增加一個緩存節點 N4 150,也可以放到如下圖的位置。

 

 

這種演算法對於增加緩存數據,和緩存節點的開銷相對比較小。

5.1.3. Range Based 演算法

這種方式是按照關鍵值(例如 ID)將數據劃分成不同的區間,每個緩存節點負責一個或者多個區間。跟一致性哈希有點像。

例如:存在三個緩存節點分別是 N1,N2,N3。他們用來存放數據的區間分別是,N1(0, 100], N2(100, 200], N3(300, 400]。

那麼數據根據自己 ID 作為關鍵字做 Hash 以後的結果就會分別對應放到這幾個區域裡面了。
 關註公眾號可獲取一線互聯網公司面試題目額


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 再此我想說明一點,好多教程都是轉載別人的,而且也不註明從哪裡轉載的。每次搜點資料的時候總是跟網上刷小視頻的感覺一樣。有些人就直接把別人的東西粘貼過來了,一點改動都沒有。廢話不多說,直接上教程。(百度網盤下載鏈接:鏈接:https://pan.baidu.com/s/1ZmNWVm7DoMv5bZF1 ...
  • 1.JSP:動態網頁 靜態動態:是否隨著時間地點、用戶操作的改變而改變 動態網頁需要用到 服務端腳本語言(JSP) 2.架構 CS:Client Server 不足: a.如果軟體升級、那麼全部軟體都需要升級 b.維護麻煩:需要維護每一臺 客戶端軟體 BS:Broswer Server 客戶端可以通 ...
  • antd快速開發(Form篇) 前言 由於一直在做中台業務,後臺項目特別多,但是後臺項目的特點是:大量的列表和大量表單,重覆開發會降低效率,所以我這邊總結了一下使用 組件搭建 的快捷方法。希望能對大家有用。 傳統Form搭建 首先傳統搭建一個form表單,那麼代碼可能會是下麵這樣子 目前只有兩個表單 ...
  • url編碼本質 其實url本質就是將中文字元串進行 ,然後得到編碼後的對象轉換字元串去掉開頭的 以及末尾的 ,然後再將 轉換成 ,再將裡面內容 變成 最後將字元串 變成 舉例 python中調用庫進行url編碼和解碼 編碼 解碼 ...
  • 數組迭代方法對每個數組項進行操作,聽著挺高深,其實,就是對數組對象一次性逐一進行一種操作的一種叫法。(文章來源:www.sysoft.net.cn,加v:15844800162深度交流) Array.forEach() forEach() 方法為每個數組元素調用一次函數(回調函數)。 實例 var ...
  • 所有瀏覽器支持的顏色名稱,所有現代瀏覽器都支持以下140種顏色名稱(單擊顏色名稱或十六進位值,以將顏色視為背景顏色以及不同的文本顏色): 有關HTML顏色的完整概述,請訪問我們的顏色教程。 ...
  • 定義和用法 animation屬性是下列屬性的一個縮寫屬性: animation-name animation-duration animation-timing-function animation-delay animation-iteration-count animation-directi ...
  • 第1步 New 菜單 Other.. -> Maven -> Maven Project ,然後單擊 Next 。如下圖所示 第2步 在New Maven Project嚮導中,選擇Create a simple project並指定項目保存的目錄(如:F:\worksp\Maven\webproj ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...