科普信息網

緩存是什么?這篇科普文值得Mark起來

發布時間:2020-11-19 14:21:48 來源:開源中國 責任編輯:caobo

只要是位正兒八經的程序員應該都知道“緩存”是什么,甚至我司的很多做運營的小姐姐現在和程序員小哥哥交流中都時不時冒出“緩存”這個詞,讓人壓力山大。

當然,這里討論的是指軟件層面的緩存。大家都知道的一點是,緩存可以讓原本打開很慢的頁面,變得能“秒開”。你平時訪問的App與網站幾乎都有涉及到緩存的運用。

那么,緩存除了能加速數據的訪問之外,還有什么作用呢?

另外,任何事物都有兩面性,我們如何才能將緩存的優點發揮得淋漓盡致,同時避免掉到它的弊端中呢?

本文接下來就給大家分享一下如何理解緩存,以及它的運用思路,希望對你有所啟發。

緩存能做什么?

正如前面所說,大家最普遍的理解就是當我們遇到某個頁面打開很慢的時候,會想到引入緩存,這樣頁面打開就快了。

其實快和慢都是相對的,從技術角度來說,緩存之所以快是因為緩存是基于內存去建立的,而內存的讀寫速度比硬盤快X倍,所以用內存來代替硬盤作為讀寫的介質自然能大大提高訪問數據的速度。

通過在內存中存儲被訪問過的數據供后續訪問時使用,以此來達到提速的效果。

這個過程大致是這樣的,

預讀取和延遲寫。

其實除此之外,緩存還有另外2個重要的運用方式:

預讀取

在系統中先將硬盤中的一部分數據加載到內存中,然后再對外提供服務。

預讀取就是預先讀取將要載入的數據,也可以稱作“緩存預熱”,它是

為什么要這樣做呢?因為有些系統一旦啟動就要面臨上千上萬的請求進來(在一些toC的項目尤其如此),如果直接讓這些請求打到數據庫上,非常大的可能是數據庫壓力暴增,直接被干趴,無法正常響應。

為了緩解這個問題,就需要通過“預讀取”來解決。

可能你會問,哪怕用了緩存還是扛不住呢?那就是做橫向擴展+負載均衡的時候到了,這不是本文討論的內容,有機會再專門分享吧。

如果說“預讀取”是在“數據出口”加了一道前置的緩沖區的話,那么下面要說的“延遲寫”就是在“數據入口”后面加了一道后置的緩沖區。

延遲寫

你可能知道,數據庫的寫入速度是慢于讀取速度的,因為寫入的時候有一系列的保證數據準確性的機制。

所以,如果想提升寫入速度的話,要么做分庫分表,要么就是通過緩存來進行一道緩沖,再一次性批量寫到磁盤,以此來提速。

題外話:由于分庫分表對跨表操作以及多條件組合查詢的副作用巨大,所以引入它的復雜度遠大于引入緩存,我們應當優先考慮引入緩存的方案。

預先將需要寫入到磁盤或者數據庫的數據,暫時寫入到內存,然后就返回成功,再定時將內存中的數據批量寫入到磁盤。

那么,通過緩存機制來加速“寫”的過程就可以稱作“延遲寫”,它是

可能你會想,寫到內存就認為成功,萬一中途出現意外、斷電、停機等導致程序異常終止的情況,數據不就丟了嗎?

一般僅用于對數據完整性要求不是那么苛刻的場景

是的。所以“延遲寫”,比如點贊數啊、參與用戶數啊等等,可以大大緩解對數據庫頻繁修改所帶來的壓力。

其實在我們熟知的分布式緩存Redis中,其默認運用的持久化機制——RDB,也是這樣的思路。

在一個成熟的系統中,能夠運用到緩存的地方其實并不是一處。下面就來梳理一下我們在哪些地方可以“加緩存”。

哪里可以加緩存?

在說哪里可以加緩存之前我們先搞清楚一個事情,我們要緩存什么?也就是符合什么特點的數據才需要加緩存?畢竟加緩存是一個額外的成本投入,得物有所值。

一般來說你可以用這兩個標準來判斷:

熱點數據:被高頻訪問,如幾十次/秒以上

靜態數據:很少變化,讀遠大于寫,如幾天變更一次

接下去就可以替它們找到合適的地方加緩存了。

選擇在哪里加緩存就相當于選擇在一條馬路的哪個位置設路障。

緩存的本質是一個“防御性”的機制,而系統之間的數據流轉是一個有序的過程,所以,在這個路障之后的道路都能受到保護,不被車流碾壓。

那么在以終端用戶為起點,系統所用的數據庫為終點的這條道路上可以作為緩存設立點的位置大致有以下這些:

每個設立點可以擋掉一些流量,最終形成一個漏斗狀的攔截效果,以此保護最后面的系統以及最終的數據庫。

下面簡要描述一下每個運用場景以及需要注意的點。

瀏覽器緩存

這是離用戶最近的可以作為緩存的地方,而且借助的是用戶的“資源”(緩存的數據在用戶的終端設備上),性價比可謂最好,讓用戶幫你分擔壓力。

當你打開瀏覽器的開發者工具,看到from cache或者from memory cache、from disk cache的時候,就意味著這些數據已經被緩存在了用戶的終端設備上了,沒網的時候也能訪問到一部分內容就是這個原因。

這個過程是瀏覽器替我們完成的,一般用于緩存圖片、js與css這些資源,我們可以通過Http消息頭中的Cache-Control來控制它,具體細節這里就不展開了。此外,js里的全局變量、cookie等運用也屬于該范疇。

瀏覽器緩存是在于用戶側的緩存點,所以我們對它的掌控力會比較差,在沒有發起新請求的情況下,你無法主動去更新數據。

CDN緩存

提供CDN服務的服務商,在全國甚至是全球部署著大量的服務器節點(可以叫做“邊緣服務器”)。

那么將數據分發到這些遍布各地服務器上作為緩存,讓用戶訪問就近的服務器上的緩存數據,就可以起到壓力分攤和加速效果。這在toC類型的系統上運用,效果格外顯著。

但是需要注意的是,由于節點眾多,更新緩存數據比較緩慢,一般至少是分鐘級別,所以一般僅適用于不經常變動的靜態數據。

題外話:解決方式也是有的,就是在url后面帶個自增數或者唯一標示,如?v=1001。因為不同的url會被視作“新”的數據和文件,被重新create出來。

網關(代理)緩存

到這里做緩存就是在你自己的地盤了。很多時候我們會在源站前面架一層網關(或者說反向代理、正向代理),為的是做一些安全機制或者作為統一分流策略的入口。

同時這里也是做緩存的一個好場所,畢竟網關是“業務無關性”的,它能夠攔下來請求,對背后的源站也有很大的受益,減少了大量的CPU運算。

常用的網關(代理)緩存有Varnish、Squid與Ngnix。一般情況下,簡單的緩存運用場景,用Nginx即可,因為大部分時候我們會用它來做負載均衡,能少引入一個技術就少一份復雜度。如果是大量的小文件可以使用Varnish,而Squid則相對大而全,運用成本也更高一些。

進程內緩存

可能我們大多數程序員第一次刻意使用緩存的場景就是這個時候。

一個請求能走到這里說明它是“業務相關”的,需要經過業務邏輯的運算。

也正因為如此,從這里開始對緩存的引入成本比前面3種大大增加,因為對緩存與數據庫之間的“數據一致性”要求更高了。

進程外緩存

這個大家也熟悉,就是Redis與Memcached之類,甚至也可以自己單獨寫一個程序來專門存放緩存數據,供其它程序遠程調用。

這里先多說幾句關于Redis和Memcached該怎么選擇的思路。

對資源(cpu、內存等)利用率格外重視的話可以使用Memcached,但程序在使用的時候需要容忍可能發生的數據丟失,因為是純內存的機制。如果無法容忍這點,并且對資源利用率也比較豪放的話可以使用Redis。而且Redis的數據庫結構更多,Memcached只有key-value,更像是一個NoSQL存儲。

數據庫緩存

數據庫本身是自帶緩存模塊的,否則也不會叫它內存殺手,基本上你給多少內存就能吃多少。數據庫緩存是數據庫的內部機制,一般都會給出設置緩存空間大小的配置來讓你進行干預。

最后,其實磁盤本身也有緩存。所以你會發現,為了讓數據能夠平穩地寫到物理磁盤中真的是一波三折,不知道什么時候可以有“快”到不需要程序來考慮緩存的磁盤出現來拯救我們程序員呢。

緩存是銀彈嗎?

可能你會想緩存那么好,那么應該多多益善,只要慢就上緩存來解決?

一個事物看上去再好,也有它負面的一面,緩存也有一系列的副作用需要考慮。除了前面提到的“緩存更新”和“緩存與數據的一致性”問題,還有諸如下邊的這些問題:

緩存雪崩。在大量的請求并發進入時,由于某些原因未起到預期的緩沖效果,哪怕只是很短的一段時間,導致請求全部流轉到數據庫,造成數據庫壓力過重。解決它可以通過“加鎖排隊”或者“緩存時間增加隨機值”。

緩存穿透。和“緩存雪崩”比較類似,區別是這會持續更長的時間,因為每次“cache miss”后依然無法從數據源加載數據到緩存,導致持續產生“cache miss”。解決它可以通過“布隆過濾器”或者“緩存空對象”。

緩存并發。一個緩存Key下的數據被同時set,怎么保證業務的準確性?再加上數據庫的話呢?進程內緩存、進程外緩存與數據庫三者皆用的情況下呢?用一句話來概括建議的方案是:使用“先DB再緩存”的方式,并且緩存操作用delete而不是set。

緩存無底洞。雖然分布式緩存是可以無限橫向擴展的,但是,集群下的節點真的是越多越好嗎?當然不是,緩存也是符合“邊際效應遞減”規律的。

緩存淘汰。內存總是有限的,如果數據量很大,那么根據具體的場景定制合理的淘汰策略是必不可少的,如LRU、LFU與FIFO等等。

所以緩存不是銀彈,對緩存的使用也需要先考慮各種問題。總結一下,本文先向你介紹了運用緩存的三種思路,然后梳理了在一個完整的系統中可以設立緩存的幾個位置,并且分享了關于瀏覽器、CDN與網關(代理)等緩存的一些使用經驗,沒有具體展開來講細節,只是希望你對緩存有一個更加體系化的認識,希望能讓你看得更加全面。

標簽: 緩存是什么

上一篇:生活小百科:客家人是什么意思?
下一篇:衛龍食品計劃明年香港IPO 不排除走向更大的國際市場

新聞排行