自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

圖文并茂 | 5W1H分析法幫你系統(tǒng)掌握緩存

存儲 存儲架構(gòu)
在計算中,緩存是一個高速數(shù)據(jù)存儲層,其中存儲了數(shù)據(jù)的子集,且通常是短暫性存儲,這樣日后再次請求該數(shù)據(jù)時,直接讀緩存會比重新計算結(jié)果或讀數(shù)據(jù)存儲更快。

來,先上文章的目錄,讓大家可以對 緩存 這塊知識先建立一個系統(tǒng)性的認知,然后我會按點逐個擊破,讀者們也可以按需閱讀哈!

文章目錄

一、什么是緩存(What)

維基百科對緩存的定義是:

In computing, a cache is a hardware or software component that stores data so that future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored elsewhere.

簡而言之,緩存就是存儲數(shù)據(jù)副本或計算結(jié)果的組件,以便后續(xù)可以更快地訪問。

有緩存 VS 無緩存

在計算中,緩存是一個高速數(shù)據(jù)存儲層,其中存儲了數(shù)據(jù)的子集,且通常是短暫性存儲,這樣日后再次請求該數(shù)據(jù)時,直接讀緩存會比重新計算結(jié)果或讀數(shù)據(jù)存儲更快。通過緩存,你可以高效地重用之前檢索或計算的數(shù)據(jù)。

二、為什么要使用緩存(Why)

從定義上可以看出所謂緩存其實是其他數(shù)據(jù)的副本,使用緩存是為了更快地檢索或計算數(shù)據(jù)。

1.硬件層面:如CPU中的高速緩存

CPU VS 內(nèi)存

從上圖可以看出CPU(藍色)和內(nèi)存(粉紅色)之間存在巨大的性能差距。

在CPU訪問數(shù)據(jù)和指令遵循計算機系統(tǒng)的局部性原理:

  • 時間局部性:CPU 通常使用的許多數(shù)據(jù)會被多次使用。
  • 空間局部性:CPU 使用的許多數(shù)據(jù)通常在物理上接近以前使用的數(shù)據(jù)。

使用高速緩存可以彌補CPU和內(nèi)存之間的性能差異,減少 CPU 浪費計算時間等待內(nèi)存數(shù)據(jù)。

2.軟件層面

  • 為緩解 CPU 壓力而做緩存:比如把方法運行結(jié)果存儲起來、把原本要實時計算的內(nèi)容提前算好、把一些公用的數(shù)據(jù)進行復(fù)用,這可以節(jié)省 CPU 算力,順帶提升響應(yīng)性能。
  • 為緩解 I/O 壓力而做緩存:比如把原本對網(wǎng)絡(luò)、磁盤等較慢介質(zhì)的讀寫訪問變?yōu)閷?nèi)存等較快介質(zhì)的訪問,將原本對單點部件(如數(shù)據(jù)庫)的讀寫訪問變?yōu)榈娇蓴U縮組件(如緩存中間件)的訪問,順帶提升響應(yīng)性能。

3.產(chǎn)品層面

是否可以解決用戶的痛點問題決定著用戶會不會一開始嘗試使用某款產(chǎn)品,是否有極致的用戶體驗影響用戶會不會持續(xù)使用某款產(chǎn)品。在業(yè)務(wù)中使用緩存的目的就是通過擴大系統(tǒng)吞吐量、減少時延和響應(yīng)時間來優(yōu)化用戶體驗,適當(dāng)?shù)男阅軆?yōu)化可以提升體驗,增強用戶粘性。

在現(xiàn)有的互聯(lián)網(wǎng)應(yīng)用中,緩存的使用是一種能夠提升服務(wù)快速響應(yīng)的關(guān)鍵技術(shù),也是產(chǎn)品經(jīng)理無暇顧及的非功能需求,需要在設(shè)計技術(shù)方案時對業(yè)務(wù)場景,具有一定的前瞻性評估后,決定在技術(shù)架構(gòu)中是否需要引入緩存解決這種這種非功能需求。

三、什么時候使用緩存(When)

緩存不是架構(gòu)設(shè)計的必選項,也不是業(yè)務(wù)開發(fā)中的必要功能點,只有在業(yè)務(wù)出現(xiàn)性能瓶頸,進行優(yōu)化性能的時候才需要考慮使用緩存來提升系統(tǒng)性能。并非所有的業(yè)務(wù)場景都適合用緩存,讀多寫少、不要求一致性、時效要求越低、訪問頻率越高、對最終一致性和數(shù)據(jù)丟失有一定程度的容忍的場景才適合使用緩存,緩存并不能解決所有的性能問題,倘若濫用緩存會帶來額外的維護成本,使得系統(tǒng)架構(gòu)更復(fù)雜更難以維護。

雖然緩存適用于各種各樣的案例,但要充分利用緩存,需要進行一定的規(guī)劃。所以在決定是否緩存一段數(shù)據(jù)時,請考慮以下問題:

  • 使用緩存值是否安全? 相同的數(shù)據(jù)在不同的上下文中可能有不同的一致性要求。例如電商系統(tǒng)中,在線結(jié)賬期間,必須知道商品的確切價格,因此不適合使用緩存,但在其他頁面上,價格晚幾分鐘更新不會給用戶帶來負面影響。
  • 對于該數(shù)據(jù)而言,緩存是否高效? 某些應(yīng)用程序會生成不適合緩存的訪問模式;例如,掃描頻繁變化的大型數(shù)據(jù)集的鍵空間。在這種情況下,保持緩存更新可能會抵消緩存帶來的所有優(yōu)勢。
  • 數(shù)據(jù)結(jié)構(gòu)是否適合緩存?例如:以單條數(shù)據(jù)庫記錄形式緩存數(shù)據(jù)通常足以提供顯著的性能優(yōu)勢。但有些時候,數(shù)據(jù)最好以多條記錄組合在一起的格式進行緩存。緩存以簡單的鍵值形式存儲,因此您可能還需要以多種不同格式緩存數(shù)據(jù)記錄,以便按記錄中的不同屬性進行訪問。

另外把緩存當(dāng)做存儲來使用是一件極其致命的做法,這種錯誤的認識,將緩存引入系統(tǒng)的那一刻起就意味著已經(jīng)讓系統(tǒng)走上了危險的局面,只有對緩存的使用邊界有深刻的理解,才能盡可能減少引入緩存帶來的副作用。

四、誰會使用緩存(Who)

其實緩存的思想隨處可見,日常生活中人們都會有意無意地用到。比如你會把??吹臅诺綍郎?,這樣你可以更快地拿到它們,而受限于桌面空間,不??吹臅鸵旁诳臻g更大的書柜里了,等到要看的時候再從書柜里拿出來放到書桌上。這里的書桌其實就是一種緩存介質(zhì)了。

對于程序員來說,緩存更是家常便飯了。有哪些程序員會用到緩存技術(shù)呢?

1.硬件開發(fā)工程師:比如CPU緩存、GPU緩存和數(shù)字信號處理器(DSP)緩存等。

2.軟件開發(fā)工程師:

  • 客戶端開發(fā):比如第4章節(jié)講到的頁面、瀏覽器、APP緩存技術(shù)都和客戶端息息相關(guān)。
  • 后端開發(fā):比如服務(wù)端本地緩存、redis和memcached充當(dāng)數(shù)據(jù)庫緩存、靜態(tài)頁面緩存等。
  • 分布式開發(fā):現(xiàn)在分布式大行其道,分布式緩存必然是分布式開發(fā)中繞不開的一環(huán),6.10節(jié)的分布式緩存也是我們要重點講解的。
  • 操作系統(tǒng)開發(fā):操作系統(tǒng)內(nèi)核負責(zé)管理磁盤緩存,比較典型的就有主存中的頁面緩存技術(shù)。

五、哪些地方會使用緩存(Where)

1.緩存分類

按照不同的維度,可以對緩存分門別類,如下圖所示。

緩存分類

下面我著重按照 緩存所處鏈路節(jié)點的位置 來系統(tǒng)梳理出不同類型的緩存應(yīng)用。

(1)客戶端緩存

HTTP 協(xié)議的無狀態(tài)性決定了它必須依靠客戶端緩存來解決網(wǎng)絡(luò)傳輸效率上的缺陷。由于每次請求都是獨立的,服務(wù)端不保存此前請求的狀態(tài)和資源,所以也不可避免地導(dǎo)致其攜帶有重復(fù)的數(shù)據(jù),造成網(wǎng)絡(luò)性能降低。HTTP 協(xié)議對此問題的解決方案便是客戶端緩存。

常見的客戶端緩存有如下幾種:

1)頁面緩存

頁面緩存是指將靜態(tài)頁面獲取頁面中的部分元素緩存到本地,以便下次請求不需要重復(fù)資源文件,h5很好的支持的離線緩存的功能,具體實現(xiàn)可通過頁面指定manifest文件,當(dāng)瀏覽器訪問一個帶有manifest屬性的文件時,會先從應(yīng)用緩存中獲取加載頁面的資源文件,并通過檢查機制處理緩存更新的問題。

2APP緩存

APP可以將內(nèi)容緩存到內(nèi)存或者本地數(shù)據(jù)庫中,例如在一些開源的圖片庫中都具備緩存的技術(shù)特性,當(dāng)圖片等資源文件從遠程服務(wù)器獲取后會進行緩存,以便下一次不再進行重復(fù)請求,并可以減少用戶的流量費用。

客戶端緩存是前端性能優(yōu)化的一個重要方向,畢竟客戶端是距離“用戶”最近的地方,是一個可以充分挖掘優(yōu)化潛力的地方。

3)瀏覽器緩存

瀏覽器緩存通常會專門開辟內(nèi)存空間以存儲資源副本,當(dāng)用戶后退或者返回上一步操作時可以通過瀏覽器緩存快速的獲取數(shù)據(jù),減少頁面加載時間和帶寬使用。在 HTTP 從 1.0 到 1.1,再到 2.0 版本的每次演進中,逐步形成了現(xiàn)在被稱為“狀態(tài)緩存”、“強制緩存”(許多資料中簡稱為“強緩存”)和“協(xié)商緩存”的 HTTP 緩存機制。在HTTP 1.1中通過引入e-tag標(biāo)簽并結(jié)合expire、cache-control兩個特性能夠很好的支持瀏覽器緩存。

下面我們用思維導(dǎo)圖理解 強制緩存 和 協(xié)商緩存,請注意體會梳理思路!

瀏覽器緩存

對于Etag的補充說明:

HTTP 服務(wù)器可以根據(jù)自己的意愿來選擇如何生成這個標(biāo)識,比如 Apache 服務(wù)器的 Etag 值默認是對文件的索引節(jié)點(INode),大小和最后修改時間進行哈希計算后得到的。

Etag 是 HTTP 中一致性最強的緩存機制,比如,Last-Modified 標(biāo)注的最后修改只能精確到秒級,如果某些文件在 1 秒鐘以內(nèi),被修改多次的話,它將不能準(zhǔn)確標(biāo)注文件的修改時間;又或者如果某些文件會被定期生成,可能內(nèi)容并沒有任何變化,但 Last-Modified 卻改變了,導(dǎo)致文件無法有效使用緩存,這些情況 Last-Modified 都有可能產(chǎn)生資源一致性問題,只能使用 Etag 解決。

Etag 卻又是 HTTP 中性能最差的緩存機制,體現(xiàn)在每次請求時,服務(wù)端都必須對資源進行哈希計算,這比起簡單獲取一下修改時間,開銷要大了很多。Etag 和 Last-Modified 是允許一起使用的,服務(wù)器會優(yōu)先驗證 Etag,在 Etag 一致的情況下,再去對比 Last-Modified,這是為了防止有一些 HTTP 服務(wù)器未將文件修改日期納入哈希范圍內(nèi)。

擴展知識:內(nèi)容協(xié)商機制

到這里為止,HTTP 的協(xié)商緩存機制已經(jīng)能很好地處理通過 URL 獲取單個資源的場景,為什么要強調(diào)“單個資源”呢?在 HTTP 協(xié)議的設(shè)計中,一個 URL 地址是有可能能夠提供多份不同版本的資源,比如,一段文字的不同語言版本,一個文件的不同編碼格式版本,一份數(shù)據(jù)的不同壓縮方式版本,等等。因此針對請求的緩存機制,也必須能夠提供對應(yīng)的支持。為此,HTTP 協(xié)議設(shè)計了以 Accept(Accept、Accept-Language、Accept-Charset、Accept-Encoding)開頭的一套請求 Header 和對應(yīng)的以 Content-(Content-Language、Content-Type、Content-Encoding)開頭的響應(yīng) Header,這些 Headers 被稱為 HTTP 的內(nèi)容協(xié)商機制。與之對應(yīng)的,對于一個 URL 能夠獲取多個資源的場景中,緩存也同樣也需要有明確的標(biāo)識來獲知根據(jù)什么內(nèi)容來對同一個 URL 返回給用戶正確的資源。這個就是 Vary Header 的作用,Vary 后面應(yīng)該跟隨一組其他 Header 的名字,比如:

1HTTP/1.1 200 OK
2Vary: Accept, User-Agent

以上響應(yīng)的含義是應(yīng)該根據(jù) MIME 類型和瀏覽器類型來緩存資源,獲取資源時也需要根據(jù)請求 Header 中對應(yīng)的字段來篩選出適合的資源版本。

(2)網(wǎng)絡(luò)緩存

網(wǎng)絡(luò)緩存位于客戶端以及服務(wù)端中間,通過代理的方式解決數(shù)據(jù)請求的響應(yīng),降低數(shù)據(jù)請求的回源率?;卦绰视址譃橐韵聝煞N:

  • 回源流量比:回源流量是代理服務(wù)器節(jié)點請求源服務(wù)器資源時產(chǎn)生流量?;卦戳髁勘?回源流量/(回源流量+用戶請求訪問的流量),比值越低,性能越好。
  • 回源請求數(shù)比:指代理服務(wù)器節(jié)點對于沒有緩存、緩存過期(可緩存)和不可緩存的請求占全部請求記錄的比例。

網(wǎng)絡(luò)緩存常見的代理形式分為兩種:web代理緩存、邊緣緩存。

在介紹網(wǎng)絡(luò)緩存之前我們先了解下前置知識----兩種服務(wù)器代理方式:正向代理、反向代理。

正向代理其實就是:客戶端通過代理服務(wù)器與源服務(wù)器進行非直接連接??蛻舳丝梢愿兄酱矸?wù)器的存在,對源服務(wù)器透明(源服務(wù)器感知不到客戶端的存在),如下圖所示:

正向代理

通信時客戶端和代理服務(wù)器要設(shè)置好代理協(xié)議,比如Socks協(xié)議或者是HTTP協(xié)議(可以設(shè)置4.1節(jié)瀏覽器緩存中的HTTP Header)。

那正向代理有什么作用呢?

  • 提高訪問速度:通常代理服務(wù)器都設(shè)置一個較大的緩沖區(qū),當(dāng)有外界的信息通過時,同時也將其保存到緩沖區(qū)中,當(dāng)其他用戶再訪問相同的信息時, 則直接由緩沖區(qū)中取出信息,傳給用戶,以提高訪問速度。
  • 控制對內(nèi)部資源的訪問:如某大學(xué)FTP(前提是該代理地址在該資源的允許訪問范圍之內(nèi))使用教育網(wǎng)內(nèi)地址段免費代理服務(wù)器,就可以用于對教育網(wǎng)開放的各類FTP下載上傳,以及各類資料查詢共享等服務(wù)。
  • 過濾、調(diào)整內(nèi)容:例如限制對特定計算機的訪問、壓縮請求包、改變請求包的語言格式等。
  • 隱藏真實IP:通過代理服務(wù)器隱藏自己的IP,但更安全的方法是利用特定的工具創(chuàng)建代理鏈(如:Tor)。
  • 突破網(wǎng)站的區(qū)域限制:通過代理服務(wù)器訪問一些被限制的網(wǎng)站。

反向代理就是:客戶端通過代理服務(wù)器與源服務(wù)器進行非直接連接??蛻舳酥粫弥聪虼淼腎P地址,而不知道在代理服務(wù)器后面的服務(wù)器集群的存在。如下圖所示:

反向代理

反向代理有什么作用呢?

  • 對于靜態(tài)內(nèi)容及短時間內(nèi)有大量訪問請求的動態(tài)內(nèi)容提供緩存服務(wù)。
  • 對客戶端隱藏服務(wù)器(集群)的IP地址。
  • 安全:作為應(yīng)用層防火墻,為服務(wù)器提供基于Web的攻擊行為(例如DoS/DDoS)的防護,更容易排查惡意軟件等
  • 為后端服務(wù)器(集群)統(tǒng)一提供加密和SSL加速(如SSL終端代理)。
  • 負載均衡:若服務(wù)器集群中有機器負荷較高,反向代理通過URL重寫,把請求轉(zhuǎn)移到低負荷機器獲取與所需相同的資源。深入了解負載均衡強烈推薦看這篇干貨:??老生常談的負載均衡,你真的懂了嗎???
  • 對一些內(nèi)容進行壓縮,以節(jié)約帶寬或為帶寬不佳的網(wǎng)絡(luò)提供正常服務(wù)。
  • 提供HTTP訪問認證。
  • 介紹完2種服務(wù)器代理形式后,我們來說下兩種網(wǎng)絡(luò)緩存形式。

1)web代理緩存:web代理緩存通常是指正向代理,會將資源文件和熱點數(shù)據(jù)放在代理服務(wù)器上,當(dāng)新的請求到來時,如果在代理服務(wù)器上能獲取數(shù)據(jù),則不需要重復(fù)請求到應(yīng)用服務(wù)器上。

2)邊緣緩存:和正向代理一樣,反向代理同樣可以用于緩存,例如nginx就提供了緩存的功能。進一步,如果這些反向代理服務(wù)器能夠做到和用戶請求來自同一個網(wǎng)絡(luò),那么獲取資源的速度進一步提升,這類的反向代理服務(wù)器可以稱之為邊緣緩存。常見的邊緣緩存就是內(nèi)容分發(fā)網(wǎng)絡(luò)(Content Delivery Network),簡稱CDN??梢詫D片等靜態(tài)資源文件放到CDN上。

那什么CDN呢?

如果把某個互聯(lián)網(wǎng)系統(tǒng)比喻為一家跨國企業(yè),那內(nèi)容分發(fā)網(wǎng)絡(luò)就是它遍布世界各地的分銷機構(gòu),如果現(xiàn)在有客戶要買一塊 CPU,那訂機票飛到美國加州英特爾總部肯定是不合適的,到本地電腦城找個裝機店鋪才是普遍的做法,在此場景中,內(nèi)容分發(fā)網(wǎng)絡(luò)就相當(dāng)于電腦城里的本地經(jīng)銷商。

那CDN的主要工作過程有哪些呢?主要包括路由解析、內(nèi)容分發(fā)、??負載均衡??(干貨文章,建議點擊閱讀)、CDN的利用場景。

路由解析

一次沒有內(nèi)容分發(fā)網(wǎng)絡(luò)參與的DNS域名解析過程如下:

無論是使用瀏覽器抑或是在程序代碼中訪問某個網(wǎng)址域名,比如以www.wallbig.club.cn為例,如果沒有緩存的話,都會先經(jīng)過 DNS 服務(wù)器的解析翻譯,找到域名對應(yīng)的 IP 地址才能開始通信,這項操作是操作系統(tǒng)自動完成的,一般不需要用戶程序的介入。不過,DNS 服務(wù)器并不是一次性地將“www.wallbig.club.cn”直接解析成 IP 地址,需要經(jīng)歷一個遞歸的過程。首先 DNS 會將域名還原為“www.wallbig.club.cn.”,注意最后多了一個點“.”,它是“.root”的含義。早期的域名必須帶有這個點才能被 DNS 正確解析,如今幾乎所有的操作系統(tǒng)、DNS 服務(wù)器都可以自動補上結(jié)尾的點號,然后開始如下解析步驟:

  • 客戶端先檢查本地的 DNS 緩存,查看是否存在并且是存活著的該域名的地址記錄。DNS 是以存活時間(Time to Live,TTL)來衡量緩存的有效情況的,所以,如果某個域名改變了 IP 地址,DNS 服務(wù)器并沒有任何機制去通知緩存了該地址的機器去更新或者失效掉緩存,只能依靠 TTL 超期后的重新獲取來保證一致性。后續(xù)每一級 DNS 查詢的過程都會有類似的緩存查詢操作。
  • 客戶端將地址發(fā)送給本機操作系統(tǒng)中配置的本地 DNS(Local DNS),這個本地 DNS 服務(wù)器可以由用戶手工設(shè)置,也可以在 DHCP 分配時或者在撥號時從 PPP 服務(wù)器中自動獲取到。
  • 本地 DNS 收到查詢請求后,會按照“是否有www.wallbig.club.cn的權(quán)威服務(wù)器”→“是否有wallbig.club.cn的權(quán)威服務(wù)器”→“是否有club.cn的權(quán)威服務(wù)器”→“是否有cn的權(quán)威服務(wù)器”的順序,依次查詢自己的地址記錄,如果都沒有查詢到,就會一直找到最后點號代表的根域名服務(wù)器為止。這個步驟里涉及了兩個重要名詞:

權(quán)威域名服務(wù)器(Authoritative DNS):是指負責(zé)翻譯特定域名的 DNS 服務(wù)器,“權(quán)威”意味著這個域名應(yīng)該翻譯出怎樣的結(jié)果是由它來決定的。DNS 翻譯域名時無需像查電話本一樣刻板地一對一翻譯,根據(jù)來訪機器、網(wǎng)絡(luò)鏈路、服務(wù)內(nèi)容等各種信息,可以玩出很多花樣,權(quán)威 DNS 的也有很多靈活應(yīng)用,后面也會講到。

根域名服務(wù)器(Root DNS)是指固定的、無需查詢的頂級域名(Top-Level Domain)服務(wù)器,可以默認為它們已內(nèi)置在操作系統(tǒng)代碼之中。全世界一共有 13 組根域名服務(wù)器(注意并不是 13 臺,每一組根域名都通過任播的方式建立了一大群鏡像,根據(jù)維基百科的數(shù)據(jù),迄今已經(jīng)超過 1000 臺根域名服務(wù)器的鏡像了)。13 這個數(shù)字是由于 DNS 主要采用 UDP 傳輸協(xié)議(在需要穩(wěn)定性保證的時候也可以采用 TCP)來進行數(shù)據(jù)交換,未分片的 UDP 數(shù)據(jù)包在 IPv4 下最大有效值為 512 字節(jié),最多可以存放 13 組地址記錄,由此而來的限制。

現(xiàn)在假設(shè)本地 DNS 是全新的,上面不存在任何域名的權(quán)威服務(wù)器記錄,所以當(dāng) DNS 查詢請求按步驟 3 的順序一直查到根域名服務(wù)器之后,它將會得到“cn的權(quán)威服務(wù)器”的地址記錄,然后通過“cn的權(quán)威服務(wù)器”,得到“club.cn的權(quán)威服務(wù)器”的地址記錄,以此類推,最后找到能夠解釋www.wallbig.club.cn的權(quán)威服務(wù)器地址。

通過“www.wallbig.club.cn的權(quán)威服務(wù)器”,查詢www.wallbig.club.cn的地址記錄,地址記錄并不一定就是指 IP 地址,在 RFC 規(guī)范中有定義的地址記錄類型已經(jīng)多達數(shù)十種,比如 IPv4 下的 IP 地址為 A 記錄,IPv6 下的 AAAA 記錄、主機別名 CNAME 記錄,等等。

下面畫個圖,方便大家理解這個解析過程:

沒有CDN參與的用戶訪問解析過程

前面提到過,每種記錄類型中還可以包括多條記錄,以一個域名下配置多條不同的 A 記錄為例,此時權(quán)威服務(wù)器可以根據(jù)自己的策略來進行選擇,典型的應(yīng)用是智能線路:根據(jù)訪問者所處的不同地區(qū)(比如華北、華南、東北)、不同服務(wù)商(比如電信、聯(lián)通、移動)等因素來確定返回最合適的 A 記錄,將訪問者路由到最合適的數(shù)據(jù)中心,達到智能加速的目的。

那如果有CDN參與的話,路由解析的具體工作過程又是哪樣的呢?

  • 架設(shè)好“wallbig.club”的服務(wù)器后,將服務(wù)器的 IP 地址在你的 CDN 服務(wù)商上注冊為“源站”,注冊后你會得到一個 CNAME,即本例中的“wallbig.club.cdn.dnsv1.com.”。
  • 將得到的 CNAME 在你購買域名的 DNS 服務(wù)商上注冊為一條 CNAME 記錄。
  • 當(dāng)?shù)谝晃挥脩魜碓L你的站點時,將首先發(fā)生一次未命中緩存的 DNS 查詢,域名服務(wù)商解析出 CNAME 后,返回給本地 DNS,至此之后鏈路解析的主導(dǎo)權(quán)就開始由內(nèi)容分發(fā)網(wǎng)絡(luò)的調(diào)度服務(wù)接管了。
  • 本地 DNS 查詢 CNAME 時,由于能解析該 CNAME 的權(quán)威服務(wù)器只有 CDN 服務(wù)商所架設(shè)的權(quán)威 DNS,這個 DNS 服務(wù)將根據(jù)一定的均衡策略和參數(shù),如拓撲結(jié)構(gòu)、容量、時延等,在全國各地能提供服務(wù)的 CDN 緩存節(jié)點中挑選一個最適合的,將它的 IP 代替源站的 IP 地址,返回給本地 DNS。
  • 瀏覽器從本地 DNS 拿到 IP 地址,將該 IP 當(dāng)作源站服務(wù)器來進行訪問,此時該 IP 的 CDN 節(jié)點上可能有,也可能沒有緩存過源站的資源,這點將在下面的“內(nèi)容分發(fā)”小節(jié)討論。
  • 經(jīng)過內(nèi)容分發(fā)后的 CDN 節(jié)點,就有能力代替源站向用戶提供所請求的資源。

為了讀者更生動地理解以上步驟,我畫個時序圖,建議和上面的圖對比來看:

CDN路由解析過程

DNS 系統(tǒng)多級分流的設(shè)計使得 DNS 系統(tǒng)能夠經(jīng)受住全球網(wǎng)絡(luò)流量不間斷的沖擊,但也并非全無缺點。典型的問題是響應(yīng)速度,當(dāng)極端情況(各級服務(wù)器均無緩存)下的域名解析可能導(dǎo)致每個域名都必須遞歸多次才能查詢到結(jié)果,顯著影響傳輸?shù)捻憫?yīng)速度。

專門有一種被稱為“DNS 預(yù)取”(DNS Prefetching)的前端優(yōu)化手段用來避免這類問題:如果網(wǎng)站后續(xù)要使用來自于其他域的資源,那就在網(wǎng)頁加載時生成一個 link 請求,促使瀏覽器提前對該域名進行預(yù)解釋,比如下面代碼所示:

<link rel="dns-prefetch" href="http://domain.not-wallbig.club">

而另一種可能更嚴(yán)重的缺陷是 DNS 的分級查詢意味著每一級都有可能受到中間人攻擊的威脅,產(chǎn)生被劫持的風(fēng)險。要攻陷位于遞歸鏈條頂層的(比如根域名服務(wù)器,cn 權(quán)威服務(wù)器)服務(wù)器和鏈路是非常困難的,它們都有很專業(yè)的安全防護措施。但很多位于遞歸鏈底層或者來自本地運營商的 Local DNS 服務(wù)器的安全防護則相對松懈,甚至不少地區(qū)的運營商自己就會主動進行劫持,專門返回一個錯的 IP,通過在這個 IP 上代理用戶請求,以便給特定類型的資源(主要是 HTML)注入廣告,以此牟利。

為此,最近幾年出現(xiàn)了另一種新的 DNS 工作模式:HTTPDNS(也稱為 DNS over HTTPS,DoH)。它將原本的 DNS 解析服務(wù)開放為一個基于 HTTPS 協(xié)議的查詢服務(wù),替代基于 UDP 傳輸協(xié)議的 DNS 域名解析,通過程序代替操作系統(tǒng)直接從權(quán)威 DNS 或者可靠的 Local DNS 獲取解析數(shù)據(jù),從而繞過傳統(tǒng) Local DNS。這種做法的好處是完全免去了“中間商賺差價”的環(huán)節(jié),不再懼怕底層的域名劫持,能夠有效避免 Local DNS 不可靠導(dǎo)致的域名生效緩慢、來源 IP 不準(zhǔn)確、產(chǎn)生的智能線路切換錯誤等問題。

內(nèi)容分發(fā)

在 DNS 服務(wù)器的協(xié)助下,無論是對用戶還是服務(wù)器,內(nèi)容分發(fā)網(wǎng)絡(luò)都可以是完全透明的,在兩者都不知情的情況下,由 CDN 的緩存節(jié)點接管了用戶向服務(wù)器發(fā)出的資源請求。后面隨之而來的問題是緩存節(jié)點中必須有用戶想要請求的資源副本,才可能代替源站來響應(yīng)用戶請求。這里面又包括了兩個子問題:“如何獲取源站資源”和“如何管理(更新)資源”。

CDN 獲取源站資源的過程被稱為“內(nèi)容分發(fā)”,“內(nèi)容分發(fā)網(wǎng)絡(luò)”的名字正是由此而來,可見這是 CDN 的核心價值。目前主要有以下兩種主流的內(nèi)容分發(fā)方式:

  • 主動分發(fā)(Push):分發(fā)由源站主動發(fā)起,將內(nèi)容從源站或者其他資源庫推送到用戶邊緣的各個 CDN 緩存節(jié)點上。這個推送的操作沒有什么業(yè)界標(biāo)準(zhǔn)可循,可以采用任何傳輸方式(HTTP、FTP、P2P,等等)、任何推送策略(滿足特定條件、定時、人工,等等)、任何推送時間,只要與后面說的更新策略相匹配即可。由于主動分發(fā)通常需要源站、CDN 服務(wù)雙方提供程序 API 接口層面的配合,所以它對源站并不是透明的,只對用戶一側(cè)單向透明。主動分發(fā)一般用于網(wǎng)站要預(yù)載大量資源的場景。比如雙十一之前一段時間內(nèi),淘寶、京東等各個網(wǎng)絡(luò)商城就會開始把未來活動中所需用到的資源推送到 CDN 緩存節(jié)點中,特別常用的資源甚至?xí)苯泳彺娴侥愕氖謾C APP 的存儲空間或者瀏覽器的localStorage上。
  • 被動回源(Pull):被動回源由用戶訪問所觸發(fā)全自動、雙向透明的資源緩存過程。當(dāng)某個資源首次被用戶請求的時候,CDN 緩存節(jié)點發(fā)現(xiàn)自己沒有該資源,就會實時從源站中獲取,這時資源的響應(yīng)時間可粗略認為是資源從源站到 CDN 緩存節(jié)點的時間,再加上資源從 CDN 發(fā)送到用戶的時間之和。因此,被動回源的首次訪問通常是比較慢的(但由于 CDN 的網(wǎng)絡(luò)條件一般遠高于普通用戶,并不一定就會比用戶直接訪問源站更慢),不適合應(yīng)用于數(shù)據(jù)量較大的資源。被動回源的優(yōu)點是可以做到完全的雙向透明,不需要源站在程序上做任何的配合,使用起來非常方便。這種分發(fā)方式是小型站點使用 CDN 服務(wù)的主流選擇,如果不是自建 CDN,而是購買阿里云、騰訊云的 CDN 服務(wù)的站點,多數(shù)采用的就是這種方式。

對于“CDN 如何管理(更新)資源”這個問題,同樣沒有統(tǒng)一的標(biāo)準(zhǔn)可言,盡管在 HTTP 協(xié)議中,關(guān)于緩存的 Header 定義中確實是有對 CDN 這類共享緩存的一些指引性參數(shù),比如“瀏覽器”小節(jié)HTTP header參數(shù)Cache-Control的 s-maxage,但是否要遵循,完全取決于 CDN 本身的實現(xiàn)策略。

現(xiàn)在,最常見的做法是超時被動失效與手工主動失效相結(jié)合。超時失效是指給予緩存資源一定的生存期,超過了生存期就在下次請求時重新被動回源一次。而手工失效是指 CDN 服務(wù)商一般會提供給程序調(diào)用來失效緩存的接口,在網(wǎng)站更新時,由持續(xù)集成的流水線自動調(diào)用該接口來實現(xiàn)緩存更新。

負載均衡

負載均衡就是以統(tǒng)一的接口對外提供服務(wù),但構(gòu)建和調(diào)度服務(wù)集群對用戶保持透明。深入了解負載均衡強烈推薦看這篇干貨:老生常談的負載均衡,你真的懂了嗎?

CDN的應(yīng)用場景

  • 加速靜態(tài)資源:這是 CDN 最普遍的應(yīng)用場景。
  • 安全防御:CDN 在廣義上可以視作網(wǎng)站的堡壘機,源站只對 CDN 提供服務(wù),由 CDN 來對外界其他用戶服務(wù),這樣惡意攻擊者就不容易直接威脅源站。CDN 對某些攻擊手段的防御,如對DDoS 攻擊的防御尤其有效。但需注意,將安全都寄托在 CDN 上本身是不安全的,一旦源站真實 IP 被泄漏,就會面臨很高的風(fēng)險。
  • 協(xié)議升級:不少 CDN 提供商都同時對接(代售 CA 的)SSL 證書服務(wù),可以實現(xiàn)源站是 HTTP 協(xié)議的,而對外開放的網(wǎng)站是基于 HTTPS 的。同理,可以實現(xiàn)源站到 CDN 是 HTTP/1.x 協(xié)議,CDN 提供的外部服務(wù)是 HTTP/2 或 HTTP/3 協(xié)議、實現(xiàn)源站是基于 IPv4 網(wǎng)絡(luò)的,CDN 提供的外部服務(wù)支持 IPv6 網(wǎng)絡(luò),等等。
  • 狀態(tài)緩存:CDN 不僅可以緩存源站的資源,還可以緩存源站的狀態(tài),比如源站的 301/302 轉(zhuǎn)向就可以緩存起來讓客戶端直接跳轉(zhuǎn)、還可以通過 CDN 開啟HSTS、可以通過 CDN 進行OCSP 裝訂加速 SSL 證書訪問等。有一些情況下甚至可以配置 CDN 對任意狀態(tài)碼(比如 404)進行一定時間的緩存,以減輕源站壓力,但這個操作應(yīng)當(dāng)慎重,在網(wǎng)站狀態(tài)發(fā)生改變時去及時刷新緩存。
  • 修改資源:CDN 可以在返回資源給用戶的時候修改它的任何內(nèi)容,以實現(xiàn)不同的目的。比如,可以對源站未壓縮的資源自動壓縮并修改 Content-Encoding,以節(jié)省用戶的網(wǎng)絡(luò)帶寬消耗、可以對源站未啟用客戶端緩存的內(nèi)容加上緩存 Header,自動啟用客戶端緩存,可以修改CORS的相關(guān) Header,將源站不支持跨域的資源提供跨域能力等。
  • 訪問控制:CDN 可以實現(xiàn) IP 黑/白名單功能,根據(jù)不同的來訪 IP 提供不同的響應(yīng)結(jié)果,根據(jù) IP 的訪問流量來實現(xiàn) QoS 控制、根據(jù) HTTP 的 Referer 來實現(xiàn)防盜鏈等。
  • 注入功能:CDN 可以在不修改源站代碼的前提下,為源站注入各種功能。

六、緩存必知必會

1.緩存擊穿

緩存擊穿是一個失效的熱點Key被并發(fā)集中訪問,導(dǎo)致請求全部打在數(shù)據(jù)庫上。圖如下:

緩存擊穿

解決方案:

(1)熱點key緩存不失效:對熱點key可以設(shè)置永不過期。

(2)使用互斥鎖或堵塞隊列:這樣可以控制數(shù)據(jù)庫的線程訪問數(shù),減小數(shù)據(jù)庫的壓力,但也會讓系統(tǒng)吞吐率有所下降。實現(xiàn)流程如下:

  • 阻塞當(dāng)前 Key 的請求
  • 從后端存儲恢復(fù)數(shù)據(jù)
  • 在Key 對應(yīng)的Value 還未恢復(fù)的過程中, 如果有其他請求繼續(xù)獲取該 Key, 同樣阻塞該請求
  • 當(dāng)key 從后端恢復(fù)后,依次喚醒該 Key 對應(yīng)阻塞的請求

(3)用堵塞隊列來實現(xiàn)的話可以參考Golang官方庫singleflight

熱點數(shù)據(jù)由代碼來手動管理,緩存擊穿是僅針對熱點數(shù)據(jù)被自動失效才引發(fā)的問題,對于這類數(shù)據(jù),可以直接由開發(fā)者通過代碼來有計劃地完成更新、失效,避免由緩存策略來自動管理。

2.緩存雪崩

某個時刻熱點數(shù)據(jù)出現(xiàn)大規(guī)模的緩存失效,大量的請求全部打到數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫瞬時壓力過載從而拒絕服務(wù)甚至是宕機。

緩存雪崩

分析:造成緩存雪崩的關(guān)鍵在于在同一時間大規(guī)模的key失效。誘因可能是:

(1)大量熱點數(shù)據(jù)設(shè)置了相同或相近的過期時間。

(2)緩存組件不可用,比如redis宕機了。

解決方案:

(1)打散緩存失效時間:主要是通過對key的TTL增加隨機數(shù)去盡量規(guī)避,過期時間則需要根據(jù)業(yè)務(wù)場景去設(shè)置。

(2)使用多級緩存:這里有個github開源庫:hybridcache可以借鑒下

(3)兜底邏輯使用熔斷機制。防止過多請求同時打到DB。

在觸發(fā)熔斷機制后返回預(yù)先配置好的兜底數(shù)據(jù),減少過多請求壓倒DB導(dǎo)致服務(wù)不可用。

熔斷檢測+數(shù)據(jù)兜底

(4)組件高可用:

  • 對于redis這樣的緩存組件,可以搭建Redis集群(集群模式或哨兵模式),提高Redis的可用性,盡量規(guī)避單點故障導(dǎo)致緩存雪崩。

redis哨兵模式

Redis基于一個Master主節(jié)點多Slave從節(jié)點的模式和Redis持久化機制,將一份數(shù)據(jù)保持在多個實例中,從而實現(xiàn)增加副本冗余量,又使用哨兵機制實現(xiàn)主備切換, 在master故障時,自動檢測,將某個slave切換為master,最終實現(xiàn)Redis高可用

  • 提高數(shù)據(jù)庫的容災(zāi)能力,可以使用分庫分表,讀寫分離的策略。

3.緩存穿透

緩存穿透是指用戶查詢數(shù)據(jù)庫沒有的數(shù)據(jù),緩存中自然也不會有。那先查緩存再查數(shù)據(jù)相當(dāng)于進行了兩次無效操作。大量的無效請求將給數(shù)據(jù)庫帶來極大的訪問壓力,甚至導(dǎo)致其過載拒絕服務(wù)。下圖中紅色箭頭標(biāo)出了每一次用戶請求到來都會經(jīng)過的兩次無效查詢。

緩存穿透

分析:造成緩存穿透的原因可能是:

(1)空數(shù)據(jù)查詢(黑客攻擊),空數(shù)據(jù)查詢通常指攻擊者偽造大量不存在的數(shù)據(jù)進行訪問(比如不存在的商品信息、用戶信息)

(2)緩存污染(網(wǎng)絡(luò)爬蟲),緩存污染通常指在遍歷數(shù)據(jù)等情況下冷數(shù)據(jù)把熱數(shù)據(jù)驅(qū)逐出內(nèi)存,導(dǎo)致緩存了大量冷數(shù)據(jù)而熱數(shù)據(jù)被驅(qū)逐。

解決方案:

(1)對于空數(shù)據(jù)查詢:

1)使用布隆過濾器高效判斷key是否存在,流程如如下:

布隆過濾器方案

雖然不能完全避免數(shù)據(jù)穿透的現(xiàn)象,但已經(jīng)可以將99%的穿透查詢給屏蔽在Redis層了,極大的降低了底層數(shù)據(jù)庫的壓力,減少了資源浪費(布隆過濾器用bitmap實現(xiàn))。如果想擁有更高的空間利用率和更小的誤判,替代的方案是使用布谷鳥過濾器。如果想了解這兩種過濾器的相關(guān)細節(jié),強烈推薦看這篇文章:??作為一名后臺開發(fā),你必須知道的兩種過濾器??

由于布隆過濾器存在“誤報”和“漏報”,而且實現(xiàn)也比較復(fù)雜,對于空數(shù)據(jù)查詢其實還有一種簡單粗暴的策略:

2)緩存空值,流程圖如下:

當(dāng)碰到查詢結(jié)果為空的key時,放一個空值到緩存中,下次再訪問就知道此key是無效的,避免無效查詢數(shù)據(jù)庫。但這樣花費額外的空間來存儲空值。

(2)對于緩存污染:關(guān)鍵點是能識別出只訪問一次或者訪問次數(shù)很少的數(shù)據(jù)。然后使用淘汰策略去刪除冷數(shù)據(jù),下面以redis為例:

緩存淘汰策略

解決緩存污染

noeviction

不能

volatile-ttl

volatile-random

不能

volatile-lru

不能

volatile-lfu

allkeys-random

不能

allkeys-lru

不能

allkeys-lfu

  • noeviction策略:不會淘汰數(shù)據(jù),解決不了。
  • volatile-ttl策略:給數(shù)據(jù)設(shè)置合理的過期時間。當(dāng)緩存寫滿時,會淘汰剩余存活時間最短的數(shù)據(jù),避免滯留在緩存中從而造成污染。
  • volatile-random策略:隨機選擇數(shù)據(jù),無法把不再訪問的數(shù)據(jù)篩選出來,會造成緩存污染。
  • volatile-lru策略:LRU策略只考慮數(shù)據(jù)的訪問時效,對只訪問一次的數(shù)據(jù),不能很快篩選出來。
  • volatile-lfu策略:LFU策略在LRU策略基礎(chǔ)上進行了優(yōu)化,篩選數(shù)據(jù)時優(yōu)先篩選并淘汰訪問次數(shù)少的數(shù)據(jù)。
  • allkeys-random策略:隨機選擇數(shù)據(jù),無法把不再訪問的數(shù)據(jù)篩選出來,會造成緩存污染。
  • allkeys-lru策略:LRU策略只考慮數(shù)據(jù)的訪問時效,對只訪問一次的數(shù)據(jù),不能很快篩選出來。
  • allkeys-lfu策略:LFU策略在LRU策略基礎(chǔ)上進行了優(yōu)化,篩選數(shù)據(jù)時優(yōu)先篩選并淘汰訪問次數(shù)少的數(shù)據(jù)。

4.緩存預(yù)熱

緩存預(yù)熱是指系統(tǒng)上線后,提前將熱點數(shù)據(jù)加載到緩存系統(tǒng)。避免在用戶請求的時候,先查詢數(shù)據(jù)庫,然后再將數(shù)據(jù)緩存的問題,在線上高并發(fā)訪問時可以提高數(shù)據(jù)訪問速度和減小數(shù)據(jù)庫壓力。

解決方案:

(1)對于單點緩存

  • 寫個緩存刷新頁面,上線時手工操作。
  • 數(shù)據(jù)量不大時,可以在程序啟動時加載。
  • 用定時器定時刷新緩存,或者模擬用戶觸發(fā)。

(2)對于分布式緩存系統(tǒng),如Redis

  • 寫程序或腳本往緩存中加載熱點數(shù)據(jù)。
  • 使用緩存預(yù)熱框架。

5.緩存降級

緩存降級是指當(dāng)訪問量劇增、服務(wù)出現(xiàn)問題(如響應(yīng)時間慢或不響應(yīng))或非核心服務(wù)影響到核心流程的性能時,即使是有損部分其他服務(wù),仍然需要保證主服務(wù)可用??梢詫⑵渌我?wù)的數(shù)據(jù)進行緩存降級,從而提升主服務(wù)的穩(wěn)定性。降級的目的是保證核心服務(wù)可用,即使是有損的。而且有些服務(wù)是無法降級的(如加入購物車、結(jié)算)。

以參考日志級別設(shè)置預(yù)案:

(1)一般:比如有些服務(wù)偶爾因為網(wǎng)絡(luò)抖動或者服務(wù)正在上線而超時,可以自動降級;

(2)警告:有些服務(wù)在一段時間內(nèi)成功率有波動(如在95~100%之間),可以自動降級或人工降級, 并發(fā)送告警;

(3)錯誤:比如可用率低于90%,或者數(shù)據(jù)庫連接池被打爆了,或者訪問量突然猛增到系統(tǒng)能承受的最大閥值,此時可以根據(jù)情況自動降級或者人工降級;

(4)嚴(yán)重錯誤:比如因為特殊原因數(shù)據(jù)錯誤了,此時需要緊急人工降級。

服務(wù)降級的目的,是為了防止Redis服務(wù)故障,導(dǎo)致數(shù)據(jù)庫跟著一起發(fā)生雪崩問題。因此,對于不重要的緩存數(shù)據(jù),可以采取服務(wù)降級策略,例如一個比較常見的做法就是,Redis出現(xiàn)問題,不去數(shù)據(jù)庫查詢,而是直接返回默認值給用戶。

6.緩存更新模式

(1)Cache-Aside模式

Cache-Aside是最常用的一種緩存更新模式,其邏輯如下:

  • 失效:應(yīng)用程序先從cache取數(shù)據(jù),沒有得到,則從數(shù)據(jù)庫中取數(shù)據(jù),成功后,放到緩存中。(圖1)
  • 命中:應(yīng)用程序從cache中取數(shù)據(jù),取到后返回。(圖1)
  • 更新:先把數(shù)據(jù)存到數(shù)據(jù)庫中,成功后,再讓緩存失效。(圖2)

Cache-Aside

注意,Cache-Aside更新操作是先更新數(shù)據(jù)庫,成功后,讓緩存失效。

如果是先刪除緩存,然后再更新數(shù)據(jù)庫,會有什么影響呢?

我們可以思考一下,假設(shè)有兩個并發(fā)操作,一個是更新操作,另一個是查詢操作,更新操作刪除緩存后,查詢操作沒有命中緩存,先把老數(shù)據(jù)讀出來后放到緩存中,然后更新操作更新了數(shù)據(jù)庫。于是,在緩存中的數(shù)據(jù)還是老的數(shù)據(jù),導(dǎo)致緩存中的數(shù)據(jù)是臟的,而且還一直這樣臟下去了。

那么Cache-Aside是否會出現(xiàn)上面提到的問題呢?

我們可以腦補一下,假如有兩個并發(fā)操作:查詢和更新,首先,沒有了刪除cache數(shù)據(jù)的操作了,而是先更新了數(shù)據(jù)庫中的數(shù)據(jù),此時,緩存依然有效,所以,并發(fā)的查詢操作拿的是沒有更新的數(shù)據(jù),但是,更新操作馬上讓緩存的失效了,后續(xù)的查詢操作再把數(shù)據(jù)從數(shù)據(jù)庫中拉出來。就不會出現(xiàn)后續(xù)的查詢操作一直都在取老的數(shù)據(jù)的問題。

這是標(biāo)準(zhǔn)的design pattern,包括Facebook的論文《Scaling Memcache at Facebook》也使用了這個策略。為什么不是寫完數(shù)據(jù)庫后更新緩存?你可以看一下Quora上的這個問答《Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?》,主要是怕兩個并發(fā)的寫操作導(dǎo)致臟數(shù)據(jù)。

那么,是不是Cache Aside這個就不會有并發(fā)問題了?不是的,比如,一個是讀操作,但是沒有命中緩存,然后就到數(shù)據(jù)庫中取數(shù)據(jù),此時來了一個寫操作,寫完數(shù)據(jù)庫后,讓緩存失效,然后,之前的那個讀操作再把老的數(shù)據(jù)放進去,所以,會造成臟數(shù)據(jù)。

這種case理論上會出現(xiàn),不過,實際上出現(xiàn)的概率可能非常低,因為這個條件需要發(fā)生在讀緩存時緩存失效,而且并發(fā)著有一個寫操作。而實際上數(shù)據(jù)庫的寫操作會比讀操作慢得多,而且還要鎖表,而讀操作必需在寫操作前進入數(shù)據(jù)庫操作,而又要晚于寫操作更新緩存,所有的這些條件都具備的概率基本并不大。

所以,這也就是Quora上的那個答案里說的,要么通過2PC或是Paxos協(xié)議保證一致性,要么就是拼命的降低并發(fā)時臟數(shù)據(jù)的概率,而Facebook使用了這個降低概率的玩法,因為2PC太慢,而Paxos太復(fù)雜。當(dāng)然,最好還是為緩存設(shè)置上過期時間。

(2) Read/Write Through模式

在上面的Cache Aside中,應(yīng)用代碼需要維護兩個數(shù)據(jù)存儲,一個是緩存(Cache),一個是數(shù)據(jù)庫(Repository)。所以,應(yīng)用程序代碼比較復(fù)雜。而Read/Write Through是把更新數(shù)據(jù)庫(Repository)的操作由緩存服務(wù)自己代理,對于應(yīng)用層來說,就簡單很多了??梢岳斫鉃?,應(yīng)用認為后端就是一個單一的存儲,而存儲自己維護自己的Cache。

1)Read-Through模式

Read-Through策略是當(dāng)緩存失效時(過期或LRU換出),緩存服務(wù)自己從數(shù)據(jù)庫加載丟失的數(shù)據(jù),填充緩存并將其返回給應(yīng)用程序。

read through

Cache-Aside 和 Read-Through策略都是延遲加載數(shù)據(jù),也就是說,只有在第一次讀取時才加載數(shù)據(jù)。

Read-Though和Cache-Aside的區(qū)別:

  • Cache-Aside策略是應(yīng)用程序負責(zé)從數(shù)據(jù)庫中獲取數(shù)據(jù)并填充到緩存。此邏輯在Read-Though中通常由庫或獨立緩存服務(wù)提供支持
  • 與Cache-Aside 不同,Read-Through cache 中的數(shù)據(jù)模型不能與數(shù)據(jù)庫中的數(shù)據(jù)模型不同。

2)Write-Through模式

Write Through 套路和Read Through相仿,不過是在更新數(shù)據(jù)時發(fā)生。當(dāng)有數(shù)據(jù)更新的時候,如果沒有命中緩存,直接更新數(shù)據(jù)庫,然后返回。如果命中了緩存,則更新緩存,然后再由Cache自己更新數(shù)據(jù)庫(這是一個同步操作)。

write through

就其本身而言,Write-Through似乎沒有太大作用,而且它會引入額外的寫入延遲,因為數(shù)據(jù)首先寫入緩存,然后寫入主數(shù)據(jù)庫。它一般和Read-Though配對使用,這樣可以獲得數(shù)據(jù)一致性保證而且免于應(yīng)用程序和緩存失效、更新、集群管理等復(fù)雜的問題。

DynamoDB Accelerator (DAX) 是通讀/直寫緩存的一個很好的例子。它連接 DynamoDB 和你的應(yīng)用程序。對 DynamoDB 的讀取和寫入可以通過 DAX 完成。

(3)Write Behind Caching模式

Write Behind也叫 Write Back。其實Linux文件系統(tǒng)的Page Cache算法用的也是這個,所以說底層的東西很多時候是相通。

Write Behind的思想是:在更新數(shù)據(jù)時,只更新緩存,不更新數(shù)據(jù)庫,而我們的緩存會異步地批量更新數(shù)據(jù)庫。

write behind caching

Write Behind的優(yōu)勢有:

  • 直接操作內(nèi)存,數(shù)據(jù)的I/O操作速度極快。
  • 更新數(shù)據(jù)庫時可以異步更新,可以合并對同一數(shù)據(jù)的多次操作,性能強悍。

事物總有兩面性,Write Behind的不足有:

  • 數(shù)據(jù)不能保證強一直性,而且可能會丟失。比如在Linux/Unix系統(tǒng)下突然關(guān)機會導(dǎo)致數(shù)據(jù)丟失。
  • 實現(xiàn)邏輯比較復(fù)雜。因為需要監(jiān)視哪些數(shù)據(jù)發(fā)生被更新了,并且在適當(dāng)?shù)臅r機持久化這些數(shù)據(jù)。比如操作系統(tǒng)的Write Behind會在僅當(dāng)這個cache需要失效的時候,才會被真正持久起來,比如,內(nèi)存不夠了,或是進程退出了等情況,這又叫l(wèi)azy write

沒有完美的方案,只有合適的方案。我們基本上不可能做出一個沒有缺陷的設(shè)計,軟件設(shè)計從來都是取舍Trade-Off。比如算法設(shè)計中的時間換空間、空間換時間,有時候軟件的強一致性和高性能不可兼得,高可用和高性能會有沖突等。

緩存更新這個章節(jié),我們都沒有考慮緩存(Cache)和持久層(Repository)的整體事務(wù)的問題。比如,更新Cache成功,更新數(shù)據(jù)庫失敗了怎么嗎?對于事務(wù)、分布式事務(wù)這個龐大的話題,大家如果有興趣的話歡迎私信我,后續(xù)我再出幾篇文章來分析、總結(jié)和歸納下。

7.緩存數(shù)據(jù)庫更新一致性問題

(1)概念介紹

  • 雙寫一致性:更新數(shù)據(jù)庫后保證redis緩存同步更新

(2)讀操作執(zhí)行過程:

  • 命中:讀操作先查詢cache,命中則直接返回緩存數(shù)據(jù)
  • 失效:讀操作先查詢cache,cache失效或過期導(dǎo)致未命中,則從數(shù)據(jù)庫中讀,寫到緩存后返回

(3)寫操作執(zhí)行過程:

1)寫操作更新緩存(更新緩存:先更新數(shù)據(jù)庫還是緩存?)

  • 先更新數(shù)據(jù)庫后更新緩存

A線程更新數(shù)據(jù)庫后還沒更新緩存時,B線程覆蓋掉A的數(shù)據(jù)庫值,然后A后寫入緩存覆蓋掉B的緩存值,導(dǎo)致數(shù)據(jù)不一致

  • 先更新緩存后更新數(shù)據(jù)庫

更新緩存成功,但是更新數(shù)據(jù)庫失敗導(dǎo)致數(shù)據(jù)不一致

2)寫操作刪除緩存(刪除緩存:先刪還是后刪緩存?)

  • 先寫數(shù)據(jù)庫后刪緩存(facebook的緩存)

寫入數(shù)據(jù)庫未刪緩存之前的緩存命中會是舊值(數(shù)據(jù)不一致時間很短)

A線程的緩存未命中讀取數(shù)據(jù)庫舊值后B線程寫入數(shù)據(jù)庫并刪除緩存(這里沒有刪除掉緩存,因為緩存不存在,如果存在A線程不可能緩存未命中),之后A線程才把舊值寫入緩存導(dǎo)致數(shù)據(jù)不一致(概率小:讀操作必需在寫操作前進入數(shù)據(jù)庫操作,而又要晚于寫操作更新緩存,但是讀操作時間比寫操作時間快很多)

  • 先刪緩存后寫數(shù)據(jù)庫

A線程刪掉緩存后未寫入數(shù)據(jù)庫之前,B線程讀取數(shù)據(jù)舊值,A線程寫入數(shù)據(jù)后,B更新緩存,導(dǎo)致數(shù)據(jù)不一致性

解決辦法:延時雙刪,延時雙刪策略會在寫庫前后刪除緩存中的數(shù)據(jù),并且給緩存數(shù)據(jù)設(shè)置合理的過期時間,從而可以保證最終一致性。寫流程具體如下:

  • 先刪除緩存,再寫數(shù)據(jù)庫
  • 休眠一段時間(比如500ms)
  • 再次刪除緩存

延時雙刪

這里為什么會休眠一段時間呢?這里主要是防止:在寫請求刪除緩存但還未成功寫入數(shù)據(jù)庫后,讀請求可能將舊值加載到緩存。

讀流程:先讀緩存,當(dāng)緩存未命中,再從數(shù)據(jù)庫中讀取,然后再寫入緩存。

延時雙刪雖然解決了上述討論的緩存對數(shù)據(jù)庫不一致的問題,但這種策略存在以下問題:

  • 休眠一段時間可能會對性能造成影響
  • 在第二次緩存刪除失敗后,會導(dǎo)致數(shù)據(jù)不一致,需要業(yè)務(wù)方實現(xiàn)重試機制。

休眠時間需要業(yè)務(wù)方設(shè)置(難點在時間怎么設(shè)置),并且要大于一次讀操作時間,才能在舊值加載到就緩存之后二次順利刪掉緩存。

8.緩存過期

緩存過期是個很復(fù)雜的問題。沒有解決這一問題的“靈丹妙藥”,但有幾個簡單的策略可供參考:

  • 始終對所有緩存鍵設(shè)置生存時間 (TTL),但Write-Through緩存更新策略(6.6節(jié)中有講到)除外??梢詫⑸鏁r間設(shè)置成很長的時間,例如數(shù)小時,甚至數(shù)天。這種方法能夠捕獲應(yīng)用程序錯誤,例如在更新底層數(shù)據(jù)庫時,忘記更新或刪除給定的緩存鍵。最終,緩存鍵會自動過期并刷新。
  • 對于頻繁更改的數(shù)據(jù),只需設(shè)置較短的 TTL (幾秒鐘) 即可。例如評論、排行榜、活動流等,不要添加Write-Through緩存或復(fù)雜的過期邏輯。如果某條數(shù)據(jù)庫查詢在生產(chǎn)環(huán)境中被大量訪問,只需改動幾行代碼就能為此查詢添加 TTL 為 5 秒的緩存鍵。在你評估更優(yōu)雅的解決方案時讓你的應(yīng)用程序保持正常運行。
  • Ruby on Rails 團隊研究出了一種更新的模式 - 俄羅斯套娃緩存。在這種模式下,嵌套記錄通過其自有緩存鍵進行管理,頂層資源就是這些緩存鍵的集合。假設(shè)您有一個包含用戶、文章和評論的新聞網(wǎng)頁。在這種方法中,他們中的每個都是自己的緩存鍵,頁面則分別查詢每個鍵。
  • 若不確定緩存鍵是否會在數(shù)據(jù)庫更新時受到影響,只需刪除此緩存鍵。如果你使用的是延遲更新策略(Cache-Aside、Write-Through)會在需要時刷新此鍵。

有關(guān)緩存過期和俄羅斯套娃緩存的詳細介紹,請參閱 Basecamp Signal vs Noise 博客文章“俄羅斯套娃”緩存的性能影響。

還可以使用另一種模式以在下游服務(wù)不可用時一定程度提高服務(wù)的彈性,也就是使用兩個 TTL:一個軟 TTL 和一個硬 TTL。客戶端將嘗試根據(jù)軟 TTL 刷新緩存項,但如果下游服務(wù)不可用或因其他原因未響應(yīng)請求,則將繼續(xù)使用現(xiàn)有的緩存數(shù)據(jù),直至達到硬 TTL。

9.緩存淘汰

緩存回收策略可以分為:

  • 基于時間:當(dāng)某緩存超過生存時間時,則進行緩存回收。或者當(dāng)某緩存最后被訪問后超過某時間仍然沒有被訪問,則進行緩存回收。
  • 基于空間:當(dāng)緩存超過某大小時,則進行緩存回收。
  • 基于容量:當(dāng)緩存超過某存儲條數(shù)時,則進行緩存回收。

緩存淘汰算法有:

  • FIFO(First In First Out):優(yōu)先淘汰最早進入被緩存的數(shù)據(jù)。FIFO 實現(xiàn)十分簡單,但一般來說它并不是優(yōu)秀的淘汰策略,越是頻繁被用到的數(shù)據(jù),往往會越早被存入緩存之中。如果采用這種淘汰策略,很可能會大幅降低緩存的命中率。
  • LRU(Least Recent Used):優(yōu)先淘汰最久未被使用訪問過的數(shù)據(jù)。LRU 通常會采用 HashMap加雙端鏈表的雙重結(jié)構(gòu)來實現(xiàn)(如 LinkedHashMap),以 HashMap 來提供訪問接口,保證常量時間復(fù)雜度的讀取性能,以 LinkedList 的鏈表元素順序來表示數(shù)據(jù)的時間順序,每次緩存命中時把返回對象調(diào)整到 LinkedList 開頭,每次緩存淘汰時從鏈表末端開始清理數(shù)據(jù)。對大多數(shù)的緩存場景來說,LRU 都明顯要比 FIFO 策略合理,尤其適合用來處理短時間內(nèi)頻繁訪問的熱點對象。但相反,它的問題是如果一些熱點數(shù)據(jù)在系統(tǒng)中經(jīng)常被頻繁訪問,但最近一段時間因為某種原因未被訪問過,此時這些熱點數(shù)據(jù)依然要面臨淘汰的命運,LRU 依然可能錯誤淘汰價值更高的數(shù)據(jù)。
  • LFU(Least Frequently Used):優(yōu)先淘汰最不經(jīng)常使用的數(shù)據(jù)。LFU 會給每個數(shù)據(jù)添加一個訪問計數(shù)器,每訪問一次就加 1,需要淘汰時就清理計數(shù)器數(shù)值最小的那批數(shù)據(jù)。LFU 可以解決上面 LRU 中熱點數(shù)據(jù)間隔一段時間不訪問就被淘汰的問題,但同時它又引入了兩個新的問題,首先是需要對每個緩存的數(shù)據(jù)專門去維護一個計數(shù)器,每次訪問都要更新,多線程并發(fā)更新要加鎖就會帶來高昂的開銷;另一個問題是不便于處理隨時間變化的熱度變化,譬如某個曾經(jīng)頻繁訪問的數(shù)據(jù)現(xiàn)在不需要了,它也很難自動被清理出緩存。

緩存淘汰策略直接影響緩存的命中率,沒有一種策略是完美的、能夠滿足全部系統(tǒng)所需的。不過,隨著淘汰算法的發(fā)展,近年來的確出現(xiàn)了許多相對性能要更好的,也更為復(fù)雜的新算法。以 LFU 分支為例,針對它存在的兩個問題,近年來提出的 TinyLFU 和 W-TinyLFU 算法就往往會有更好的效果。

  • TinyLFU(Tiny Least Frequently Used):TinyLFU 是 LFU 的改進版本。為了緩解 LFU 每次訪問都要修改計數(shù)器所帶來的性能負擔(dān),TinyLFU 會首先采用 Sketch 對訪問數(shù)據(jù)進行分析,所謂 Sketch 是統(tǒng)計學(xué)上的概念,指用少量的樣本數(shù)據(jù)來估計全體數(shù)據(jù)的特征,這種做法顯然犧牲了一定程度的準(zhǔn)確性,但是只要樣本數(shù)據(jù)與全體數(shù)據(jù)具有相同的概率分布,Sketch 得出的結(jié)論仍不失為一種高效與準(zhǔn)確之間權(quán)衡的有效結(jié)論。借助Count–Min Sketch算法(可視為布隆過濾器的一種等價變種結(jié)構(gòu)),TinyLFU 可以用相對小得多的記錄頻率和空間來近似地找出緩存中的低價值數(shù)據(jù)。為了解決 LFU 不便于處理隨時間變化的熱度變化問題,TinyLFU 采用了基于“滑動時間窗”的熱度衰減算法,簡單理解就是每隔一段時間,便會把計數(shù)器的數(shù)值減半,以此解決“舊熱點”數(shù)據(jù)難以清除的問題。
  • W-TinyLFU(Windows-TinyLFU):W-TinyLFU 又是 TinyLFU 的改進版本。TinyLFU 在實現(xiàn)減少計數(shù)器維護頻率的同時,也帶來了無法很好地應(yīng)對稀疏突發(fā)訪問的問題,所謂稀疏突發(fā)訪問是指有一些絕對頻率較小,但突發(fā)訪問頻率很高的數(shù)據(jù),譬如某些運維性質(zhì)的任務(wù),也許一天、一周只會在特定時間運行一次,其余時間都不會用到,此時 TinyLFU 就很難讓這類元素通過 Sketch 的過濾,因為它們無法在運行期間積累到足夠高的頻率。應(yīng)對短時間的突發(fā)訪問是 LRU 的強項,W-TinyLFU 就結(jié)合了 LRU 和 LFU 兩者的優(yōu)點,從整體上看是它是 LFU 策略,從局部實現(xiàn)上看又是 LRU 策略。具體做法是將新記錄暫時放入一個名為 Window Cache 的前端 LRU 緩存里面,讓這些對象可以在 Window Cache 中累積熱度,如果能通過 TinyLFU 的過濾器,再進入名為 Main Cache 的主緩存中存儲,主緩存根據(jù)數(shù)據(jù)的訪問頻繁程度分為不同的段(LFU 策略,實際上 W-TinyLFU 只分了兩段),但單獨某一段局部來看又是基于 LRU 策略去實現(xiàn)的(稱為 Segmented LRU)。每當(dāng)前一段緩存滿了之后,會將低價值數(shù)據(jù)淘汰到后一段中去存儲,直至最后一段也滿了之后,該數(shù)據(jù)就徹底清理出緩存。

另外還有兩種高級淘汰策略ARC(Adaptive Replacement Cache)、LIRS(Low Inter-Reference Recency Set),大家有興趣可以再深入閱讀,對其他緩存淘汰策略感興趣的讀者也可以參考維基百科中對Cache Replacement Policies的介紹。

10.分布式緩存

相比起緩存數(shù)據(jù)在進程內(nèi)存中讀寫的速度,一旦涉及網(wǎng)絡(luò)訪問,由網(wǎng)絡(luò)傳輸、數(shù)據(jù)復(fù)制、序列化和反序列化等操作所導(dǎo)致的延遲要比內(nèi)存訪問高得多,所以對分布式緩存來說,處理與網(wǎng)絡(luò)有相關(guān)的操作是對吞吐量影響更大的因素,往往也是比淘汰策略、擴展功能更重要的關(guān)注點,這決定了盡管也有 Ehcache、Infinispan 這類能同時支持分布式部署和進程內(nèi)嵌部署的緩存方案,但通常進程內(nèi)緩存和分布式緩存選型時會有完全不同的候選對象及考察點。我們決定使用哪種分布式緩存前,首先必須確認自己需求是什么?

  • 從訪問的角度來說,如果是頻繁更新但甚少讀取的數(shù)據(jù),通常是不會有人把它拿去做緩存的,因為這樣做沒有收益。對于甚少更新但頻繁讀取的數(shù)據(jù),理論上更適合做復(fù)制式緩存;對于更新和讀取都較為頻繁的數(shù)據(jù),理論上就更適合做集中式緩存。筆者簡要介紹這兩種分布式緩存形式的差別與代表性產(chǎn)品:
  • 復(fù)制式緩存:復(fù)制式緩存可以看作是“能夠支持分布式的進程內(nèi)緩存”,它的工作原理與 Session 復(fù)制類似。緩存中所有數(shù)據(jù)在分布式集群的每個節(jié)點里面都存在有一份副本,讀取數(shù)據(jù)時無須網(wǎng)絡(luò)訪問,直接從當(dāng)前節(jié)點的進程內(nèi)存中返回,理論上可以做到與進程內(nèi)緩存一樣高的讀取性能;當(dāng)數(shù)據(jù)發(fā)生變化時,就必須遵循復(fù)制協(xié)議,將變更同步到集群的每個節(jié)點中,復(fù)制性能隨著節(jié)點的增加呈現(xiàn)平方級下降,變更數(shù)據(jù)的代價十分高昂。

復(fù)制式緩存的代表是JBossCache,這是 JBoss 針對企業(yè)級集群設(shè)計的緩存方案,支持 JTA 事務(wù),依靠 JGroup 進行集群節(jié)點間數(shù)據(jù)同步。以 JBossCache 為典型的復(fù)制式緩存曾有一段短暫的興盛期,但今天基本上已經(jīng)很難再見到使用這種緩存形式的大型信息系統(tǒng)了,JBossCache 被淘汰的主要原因是寫入性能實在差到不堪入目的程度,它在小規(guī)模集群中同步數(shù)據(jù)尚算差強人意,但在大規(guī)模集群下,很容易就因網(wǎng)絡(luò)同步的速度跟不上寫入速度,進而導(dǎo)致在內(nèi)存中累計大量待重發(fā)對象,最終引發(fā) OutOfMemory 崩潰。如果對 JBossCache 沒有足夠了解的話,稍有不慎就要被埋進坑里。

為了緩解復(fù)制式同步的寫入效率問題,JBossCache 的繼任者Infinispan提供了另一種分布式同步模式(這種同步模式的名字就叫做“分布式”),允許用戶配置數(shù)據(jù)需要復(fù)制的副本數(shù)量,譬如集群中有八個節(jié)點,可以要求每個數(shù)據(jù)只保存四份副本,此時,緩存的總?cè)萘肯喈?dāng)于是傳統(tǒng)復(fù)制模式的一倍,如果要訪問的數(shù)據(jù)在本地緩存中沒有存儲,Infinispan 完全有能力感知網(wǎng)絡(luò)的拓撲結(jié)構(gòu),知道應(yīng)該到哪些節(jié)點中尋找數(shù)據(jù)。

  • 集中式緩存:集中式緩存是目前分布式緩存的主流形式,集中式緩存的讀、寫都需要網(wǎng)絡(luò)訪問,其好處是不會隨著集群節(jié)點數(shù)量的增加而產(chǎn)生額外的負擔(dān),其壞處自然是讀、寫都不再可能達到進程內(nèi)緩存那樣的高性能。

集中式緩存還有一個必須提到的關(guān)鍵特點,它與使用緩存的應(yīng)用分處在獨立的進程空間中,其好處是它能夠為異構(gòu)語言提供服務(wù),譬如用 C 語言編寫的Memcached完全可以毫無障礙地為 Java 語言編寫的應(yīng)用提供緩存服務(wù);但其壞處是如果要緩存對象等復(fù)雜類型的話,基本上就只能靠序列化來支撐具體語言的類型系統(tǒng)(支持 Hash 類型的緩存,可以部分模擬對象類型),不僅有序列化的成本,還很容易導(dǎo)致傳輸成本也顯著增加。舉個例子,假設(shè)某個有 100 個字段的大對象變更了其中 1 個字段的值,通常緩存也不得不把整個對象所有內(nèi)容重新序列化傳輸出去才能實現(xiàn)更新,因此,一般集中式緩存更提倡直接緩存原始數(shù)據(jù)類型而不是對象。相比之下,JBossCache 通過它的字節(jié)碼自審(Introspection)功能和樹狀存儲結(jié)構(gòu)(TreeCache),做到了自動跟蹤、處理對象的部分變動,用戶修改了對象中哪些字段的數(shù)據(jù),緩存就只會同步對象中真正變更那部分數(shù)據(jù)。

如今Redis廣為流行,基本上已經(jīng)打敗了 Memcached 及其他集中式緩存框架,成為集中式緩存的首選,甚至可以說成為了分布式緩存的實質(zhì)上的首選,幾乎到了不必管讀取、寫入哪種操作更頻繁,都可以無腦上 Redis 的程度。也因如此,之前說到哪些數(shù)據(jù)適合用復(fù)制式緩存、哪些數(shù)據(jù)適合集中式緩存時,筆者都在開頭加了個拗口的“理論上”。盡管 Redis 最初設(shè)計的本意是 NoSQL 數(shù)據(jù)庫而不是專門用來做緩存的,可今天它確實已經(jīng)成為許多分布式系統(tǒng)中無可或缺的基礎(chǔ)設(shè)施,廣泛用作緩存的實現(xiàn)方案。

  • 從數(shù)據(jù)一致性角度說,緩存本身也有集群部署的需求,理論上你應(yīng)該認真考慮一下是否能接受不同節(jié)點取到的緩存數(shù)據(jù)有可能存在差異。譬如剛剛放入緩存中的數(shù)據(jù),另外一個節(jié)點馬上訪問發(fā)現(xiàn)未能讀到;剛剛更新緩存中的數(shù)據(jù),另外一個節(jié)點訪問在短時間內(nèi)讀取到的仍是舊的數(shù)據(jù),等等。根據(jù)分布式緩存集群是否能保證數(shù)據(jù)一致性,可以將它分為 AP 和 CP 兩種類型。此處又一次出現(xiàn)了“理論上”,是因為我們實際開發(fā)中通常不太會把追求強一致性的數(shù)據(jù)使用緩存來處理,可以這樣做,但是沒必要(可類比 MESI 等緩存一致性協(xié)議)。譬如,Redis 集群就是典型的 AP 式,有著高性能高可用等特點,卻并不保證強一致性。而能夠保證強一致性的 ZooKeeper、Doozerd、Etcd 等分布式協(xié)調(diào)框架,通常不會有人將它們當(dāng)為“緩存框架”來使用,這些分布式協(xié)調(diào)框架的吞吐量相對 Redis 來說是非常有限的。不過 ZooKeeper、Doozerd、Etcd 倒是常與 Redis 和其他分布式緩存搭配工作,用來實現(xiàn)其中的通知、協(xié)調(diào)、隊列、分布式鎖等功能。

分布式緩存與進程內(nèi)緩存各有所長,也有各有局限,它們是互補而非競爭的關(guān)系,如有需要,完全可以同時把進程內(nèi)緩存和分布式緩存互相搭配,構(gòu)成透明多級緩存(Transparent Multilevel Cache,TMC),如圖 4-14 所示。先不考慮“透明”的話,多級緩存是很好理解的,使用進程內(nèi)緩存做一級緩存,分布式緩存做二級緩存,如果能在一級緩存中查詢到結(jié)果就直接返回,否則便到二級緩存中去查詢,再將二級緩存中的結(jié)果回填到一級緩存,以后再訪問該數(shù)據(jù)就沒有網(wǎng)絡(luò)請求了。如果二級緩存也查詢不到,就發(fā)起對最終數(shù)據(jù)源的查詢,將結(jié)果回填到一、二級緩存中去。

多級緩存

盡管多級緩存結(jié)合了進程內(nèi)緩存和分布式緩存的優(yōu)點,但它的代碼侵入性較大,需要由開發(fā)者承擔(dān)多次查詢、多次回填的工作,也不便于管理,如超時、刷新等策略都要設(shè)置多遍,數(shù)據(jù)更新更是麻煩,很容易會出現(xiàn)各個節(jié)點的一級緩存、以及二級緩存里數(shù)據(jù)互相不一致的問題。必須“透明”地解決以上問題,多級緩存才具有實用的價值。一種常見的設(shè)計原則是變更以分布式緩存中的數(shù)據(jù)為準(zhǔn),訪問以進程內(nèi)緩存的數(shù)據(jù)優(yōu)先。大致做法是當(dāng)數(shù)據(jù)發(fā)生變動時,在集群內(nèi)發(fā)送推送通知(簡單點的話可采用 Redis 的 PUB/SUB,求嚴(yán)謹?shù)脑捯?ZooKeeper 或 Etcd 來處理),讓各個節(jié)點的一級緩存自動失效掉相應(yīng)數(shù)據(jù)。當(dāng)訪問緩存時,提供統(tǒng)一封裝好的一、二級緩存聯(lián)合查詢接口,接口外部是只查詢一次,接口內(nèi)部自動實現(xiàn)優(yōu)先查詢一級緩存,未獲取到數(shù)據(jù)再自動查詢二級緩存的邏輯。

附加解釋下CAP定理:CAP是分布式計算領(lǐng)域所公認的著名定理。其描述了一個分布式的系統(tǒng)中,涉及共享數(shù)據(jù)問題時,以下三個特性最多只能同時滿足其中兩個:

  • 一致性(Consistency):代表數(shù)據(jù)在任何時刻、任何分布式節(jié)點中所看到的都是符合預(yù)期的。
  • 可用性(Availability):代表系統(tǒng)不間斷地提供服務(wù)的能力,理解可用性要先理解與其密切相關(guān)兩個指標(biāo):可靠性(Reliability)和可維護性(Serviceability)??煽啃允褂闷骄鶡o故障時間(Mean Time Between Failure,MTBF)來度量;可維護性使用平均可修復(fù)時間(Mean Time To Repair,MTTR)來度量。可用性衡量系統(tǒng)可以正常使用的時間與總時間之比,其表征為:A=MTBF/(MTBF+MTTR),即可用性是由可靠性和可維護性計算得出的比例值,譬如 99.9999%可用,即代表平均年故障修復(fù)時間為 32 秒。
  • 分區(qū)容忍性(Partition Tolerance):代表分布式環(huán)境中部分節(jié)點因網(wǎng)絡(luò)原因而彼此失聯(lián)后,即與其他節(jié)點形成“網(wǎng)絡(luò)分區(qū)”時,系統(tǒng)仍能正確地提供服務(wù)的能力。

七、如何設(shè)計緩存(How)

在進行緩存結(jié)構(gòu)設(shè)計的時候,需要考慮的點有很多:

1)對緩存帶來的價值持懷疑態(tài)度:從成本、延遲和可用性等方面來評估引入緩存的合理性,并仔細評估引入緩存帶來的額外風(fēng)險和收益。

2)業(yè)務(wù)流量量級評估:對于低并發(fā)低流量的應(yīng)用而言,引入緩存并不會帶來性能的顯著提升,反而會帶來應(yīng)用的復(fù)雜度以及極高的運維成本。也不是任何數(shù)據(jù)都需要使用緩存,比如圖片視頻等文件使用分布式文件系統(tǒng)更合適而不是緩存。因此,在引入緩存前,需要對當(dāng)前業(yè)務(wù)的流量進行評估,在高并發(fā)大流量的業(yè)務(wù)場景中引入緩存相對而言收益會更高;

3)緩存組件選型:緩存應(yīng)用有很多如Redis、Memcached以及tair等等,針對每一種分布式緩存應(yīng)用的優(yōu)缺點以及適用范圍、內(nèi)存效率、運維成本甚至團隊開發(fā)人員的知識結(jié)構(gòu)都需要了解,才能做好技術(shù)選型;

4)緩存相關(guān)指標(biāo)評估:在引入緩存前,需要著重評估value大小、峰值QPS、緩存內(nèi)存空間、緩存命中率、過期時間、過期淘汰策略、讀寫更新策略、key值分布路由策略、數(shù)據(jù)一致性方案等多個因素,要做到心中有數(shù);

5)緩存高可用架構(gòu):分布式緩存要高可用,比如緩存的集群設(shè)計、主從同步方案的設(shè)計等等,只有緩存足夠可靠,才能服務(wù)于業(yè)務(wù)系統(tǒng),為業(yè)務(wù)帶來價值;

6)完善的監(jiān)控平臺:當(dāng)緩存投入生產(chǎn)環(huán)境后,需要有一套監(jiān)控系統(tǒng)能夠顯式的觀測緩存系統(tǒng)的運行情況,才能更早的發(fā)現(xiàn)問題,同時對于預(yù)估不足的非預(yù)期熱點數(shù)據(jù),也需要熱點發(fā)現(xiàn)系統(tǒng)去解決非預(yù)期的熱點數(shù)據(jù)緩存問題。

7)緩存最近訪問原則:將緩存數(shù)據(jù)放在離用戶最近的地方,無疑會極大的提升響應(yīng)的速度,這也是多級緩存設(shè)計的核心思想。

8)考慮緩存數(shù)據(jù)的安全問題。包括加密、與外部緩存隊列通信時的傳輸安全性以及緩存投毒攻擊和側(cè)信道攻擊的影響。

引用

  • coolshell:緩存更新的套路
  • 亞馬遜緩存最佳實踐
  • wikipedia:
  • 鳳凰架構(gòu):透明多級分流系統(tǒng)
  • FaceBook論文:《Scaling Memcache at Facebook》
  • Golang官方庫:singleflight
  • Github開源庫:hybridcache
  • wikipedia:
  • wikipedia:
  • wikipedia:
責(zé)任編輯:武曉燕 來源: 小梁編程匯
相關(guān)推薦

2011-01-18 18:08:28

Thunderbird

2011-01-18 18:29:28

Thunderbird

2011-11-21 15:12:54

Java斷點Eclipse

2023-08-10 07:09:07

云計算信息服務(wù)應(yīng)用系統(tǒng)

2011-01-19 17:30:21

Postfix郵件投遞

2012-07-23 14:39:27

移動

2023-05-16 08:01:13

架構(gòu)網(wǎng)站演進

2011-01-19 17:34:39

Postfix如何接收郵件

2011-01-20 09:13:18

Postfix

2021-12-27 08:04:49

架構(gòu)網(wǎng)站高并發(fā)

2011-01-14 08:46:01

2011-01-19 10:30:20

UbuntuThunderbird

2011-01-21 10:28:06

2022-07-18 14:33:05

PythonPDF報告

2011-08-03 15:21:23

ORM XCode 數(shù)據(jù)庫

2012-05-29 14:27:34

PHP

2012-07-27 10:27:19

OfficeWord

2022-04-20 18:30:00

算法架構(gòu)粗排

2011-07-04 16:57:36

QT 布局 界面

2009-07-15 14:49:16

點贊
收藏

51CTO技術(shù)棧公眾號