通鑒
個(gè)人介紹:童健2018年加入Qunar.com技術(shù)團(tuán)隊(duì)。目前在火車票事業(yè)部/技術(shù)部。個(gè)人對(duì)分布式微服務(wù)架構(gòu)和高并發(fā)系統(tǒng)感興趣,對(duì)編寫CleanCode有著執(zhí)著的追求。
一、前言在應(yīng)用中使用緩存技術(shù)可以大大減少計(jì)算量,有效提高響應(yīng)速度,讓有限的資源為更多的用戶服務(wù)。然而,似乎沒有一種緩存方案能夠滿足所有的業(yè)務(wù)場(chǎng)景。我們需要根據(jù)自己的特殊場(chǎng)景和背景選擇最合適的緩存方案,盡可能以最小的成本和最快的效率達(dá)到最好的目標(biāo)。本文將從多個(gè)方面對(duì)緩存進(jìn)行分析,從而考慮緩存方案的選擇。
二、文章要點(diǎn)理解緩存的基礎(chǔ)概念了解 CPU 緩存分布式緩存原理了解影響緩存效率的因素高并發(fā)中緩存問題的解決方案三、緩存的理解3.1 狹義的理解緩存指的是CPU緩存。CPU想讀取一個(gè)數(shù)據(jù)時(shí),先從CPU緩存中查找,立即讀取并發(fā)送給CPU處理;如果沒有找到,會(huì)以相對(duì)較慢的速度從內(nèi)存中讀取,并發(fā)送給CPU處理。同時(shí),該數(shù)據(jù)所在的數(shù)據(jù)塊將被轉(zhuǎn)移到緩存中,以便將來從緩存中讀取整個(gè)數(shù)據(jù),而不調(diào)用內(nèi)存。
3.2 廣義的理解任何位于兩種速度差異很大的硬件/軟件之間,用于協(xié)調(diào)兩者之間數(shù)據(jù)傳輸速度差異的結(jié)構(gòu)都可以稱為緩存。
3.3 緩存的優(yōu)點(diǎn)如下所示,網(wǎng)絡(luò)應(yīng)用程序架構(gòu)通常有以下幾層:
該架構(gòu)的不同級(jí)別之間可以有緩存。例如:
給數(shù)據(jù)庫(kù)加上緩存可以減少文件系統(tǒng) I/O;給應(yīng)用程序加上緩存能夠減少對(duì)數(shù)據(jù)庫(kù)的查詢;給 Web 服務(wù)器加上緩存能夠減少應(yīng)用服務(wù)器請(qǐng)求;給客戶端瀏覽器加上緩存能夠減少對(duì)網(wǎng)站的訪問。計(jì)算機(jī)在 CPU 和主存之間添加了高速緩存以加快讀取速率;操作系統(tǒng)磁盤中一般也添加了緩存,可以減少磁盤機(jī)械操作。總而言之,緩存在以下三個(gè)方面得到了改進(jìn):
性能——將相應(yīng)數(shù)據(jù)存儲(chǔ)起來以避免數(shù)據(jù)的重復(fù)創(chuàng)建、處理和傳輸,可有效提高性能。比如將不改變的數(shù)據(jù)緩存起來,例如國(guó)家列表等,這樣能明顯提高 Web 程序的反應(yīng)速度;穩(wěn)定性——同一個(gè)應(yīng)用中,對(duì)同一數(shù)據(jù)、邏輯功能和用戶界面的多次請(qǐng)求時(shí)經(jīng)常發(fā)生的。當(dāng)用戶基數(shù)很大時(shí),如果每次請(qǐng)求都進(jìn)行處理,消耗的資源是很大的浪費(fèi),也同時(shí)造成系統(tǒng)的不穩(wěn)定。例如,Web 應(yīng)用中,對(duì)一些靜態(tài)頁(yè)面的呈現(xiàn)內(nèi)容進(jìn)行緩存能有效的節(jié)省資源,提高穩(wěn)定性。而緩存數(shù)據(jù)也能降低對(duì)數(shù)據(jù)庫(kù)的訪問次數(shù),降低數(shù)據(jù)庫(kù)的負(fù)擔(dān)和提高數(shù)據(jù)庫(kù)的服務(wù)能力;可用性——有時(shí),提供數(shù)據(jù)信息的服務(wù)可能會(huì)意外停止,如果使用了緩存技術(shù),可以在一定時(shí)間內(nèi)仍正常提供對(duì)最終用戶的支持,提高了系統(tǒng)的可用性。四、CPU 緩存簡(jiǎn)介中央處理器緩存內(nèi)存是位于中央處理器和內(nèi)存之間的臨時(shí)內(nèi)存。它的容量比內(nèi)存小得多,但它的交換速度卻比內(nèi)存快得多。緩存的出現(xiàn)主要是為了解決CPU運(yùn)算速度和內(nèi)存讀寫速度的矛盾,因?yàn)镃PU運(yùn)算速度比內(nèi)存讀寫速度快很多,會(huì)讓CPU花很長(zhǎng)時(shí)間等待數(shù)據(jù)到達(dá)或者把數(shù)據(jù)寫入內(nèi)存。緩存中的數(shù)據(jù)是內(nèi)存的一小部分,但這一小部分會(huì)在短時(shí)間內(nèi)被CPU訪問。當(dāng)CPU調(diào)用大量數(shù)據(jù)時(shí),可以避開內(nèi)存,直接從緩存中調(diào)用,從而加快讀取速率??梢?,給CPU增加緩存是一種高效的解決方案,使整個(gè)內(nèi)存(cache+memory)成為一個(gè)兼具緩存率和內(nèi)存的大容量存儲(chǔ)系統(tǒng)。SRAM內(nèi)存基本上是用來緩存的,電腦內(nèi)部?jī)?nèi)存的組織如下圖所示:
內(nèi)存越高,容量越小,成本越高,速度越快。由于CPU和主存之間巨大的速度差異,系統(tǒng)設(shè)計(jì)者被迫在CPU寄存器和主存之間插入一個(gè)叫做L1緩存的小SRAM緩存,大約2-4個(gè)時(shí)鐘周期(計(jì)算機(jī)中最小的時(shí)間單位)就可以訪問。后來發(fā)現(xiàn)L1緩存和主存差距還是很大的,L1緩存和主存之間插入了L2緩存,大概10個(gè)時(shí)鐘周期就可以訪問。后來增加了L3等,這樣,在這種模式下,在不斷的進(jìn)化中形成了現(xiàn)在的存儲(chǔ)系統(tǒng)。
五、分布式緩存原理5.1 本地緩存本地緩存可能是最常用的緩存方法之一,比如Ehcache、Guava Cache等。它是應(yīng)用程序中的緩存組件。它最大的優(yōu)點(diǎn)是應(yīng)用和緩存在同一個(gè)進(jìn)程中,請(qǐng)求緩存非常快,沒有過多的網(wǎng)絡(luò)開銷等。當(dāng)單個(gè)應(yīng)用程序不需要集群支持或在集群條件下節(jié)點(diǎn)不需要相互通知時(shí),使用本地緩存更合適。同時(shí),它的缺點(diǎn)是緩存與應(yīng)用程序耦合,所以多個(gè)應(yīng)用程序不能直接共享緩存,每個(gè)應(yīng)用程序或集群的每個(gè)節(jié)點(diǎn)都需要維護(hù)自己獨(dú)立的緩存,這是對(duì)內(nèi)存的浪費(fèi)。
5.2 分布式緩存特性分布式緩存可以高性能讀取數(shù)據(jù),動(dòng)態(tài)擴(kuò)展緩存節(jié)點(diǎn),自動(dòng)發(fā)現(xiàn)和切換故障節(jié)點(diǎn),自動(dòng)平衡數(shù)據(jù)分區(qū),為用戶提供圖形化的管理界面,非常方便部署和維護(hù)。優(yōu)秀的分布式緩存系統(tǒng)包括阿里自主開發(fā)的Memcached、Redis、Tair
那么,分布式緩存是如何工作的呢?
5.3 分布式緩存實(shí)現(xiàn)原理數(shù)據(jù)讀取
分布式緩存由一臺(tái)服務(wù)器管理和控制,數(shù)據(jù)由多個(gè)客戶端節(jié)點(diǎn)存儲(chǔ),提高了數(shù)據(jù)讀取率。當(dāng)讀取某個(gè)數(shù)據(jù)時(shí),可以根據(jù)一致的哈希算法來確定數(shù)據(jù)的存儲(chǔ)和讀取節(jié)點(diǎn)。在數(shù)據(jù)d和節(jié)點(diǎn)總數(shù)n的基礎(chǔ)上,通過一致哈希算法計(jì)算數(shù)據(jù)d對(duì)應(yīng)的哈希值(相當(dāng)于門牌號(hào)),根據(jù)這個(gè)哈希值可以找到對(duì)應(yīng)的節(jié)點(diǎn)。一致哈希算法的優(yōu)點(diǎn)是,當(dāng)節(jié)點(diǎn)數(shù)量發(fā)生變化(減少或增加)時(shí),不需要重新計(jì)算哈希值,保證了在存儲(chǔ)或讀取數(shù)據(jù)時(shí)能夠正確、快速地找到對(duì)應(yīng)的節(jié)點(diǎn)。
數(shù)據(jù)的均勻分布
當(dāng)數(shù)據(jù)由多個(gè)客戶端節(jié)點(diǎn)存儲(chǔ)時(shí),需要確保數(shù)據(jù)的均勻分布。比如服務(wù)器數(shù)量少,可能會(huì)導(dǎo)致部分服務(wù)器存儲(chǔ)數(shù)據(jù)多,承受壓力大,部分服務(wù)器相對(duì)空空閑。解決方法是將一臺(tái)服務(wù)器虛擬成多臺(tái)服務(wù)器,在計(jì)算服務(wù)器對(duì)應(yīng)的哈希值時(shí),給IP地址字符串添加多個(gè)“后綴”,如:10 . 0 . 0 . 1 # 1 10 . 0 . 0 . 1 # 2 10 . 0 . 0 . 0 . 1 # 3...這樣,一臺(tái)物理服務(wù)器被虛擬化為多臺(tái)服務(wù)器。
數(shù)據(jù)熱備份
在實(shí)現(xiàn)數(shù)據(jù)熱備份之前,需要了解一致哈希算法。在計(jì)算多個(gè)服務(wù)器的IP地址哈希值時(shí),這些哈希值按順時(shí)針方向從小到大排序,形成“服務(wù)器節(jié)點(diǎn)環(huán)”。順時(shí)針看“服務(wù)器環(huán)”,當(dāng)一個(gè)客戶端在第一個(gè)服務(wù)器上存儲(chǔ)數(shù)據(jù)時(shí),第一個(gè)服務(wù)器負(fù)責(zé)將數(shù)據(jù)復(fù)制到第二個(gè)服務(wù)器上,以此類推,也就是說“服務(wù)器環(huán)”中的每個(gè)節(jié)點(diǎn)都是前一個(gè)節(jié)點(diǎn)的熱備份節(jié)點(diǎn)。同時(shí),服務(wù)器存儲(chǔ)兩種類型的數(shù)據(jù),一種是自己的業(yè)務(wù)數(shù)據(jù),另一種是前一個(gè)節(jié)點(diǎn)的熱備數(shù)據(jù)。
六、影響緩存性能因素6.1 序列化訪問本地緩存。對(duì)于JVM語(yǔ)言,您可以在堆內(nèi)緩存和堆外緩存之間進(jìn)行選擇。因?yàn)閮?nèi)部存儲(chǔ)是直接以對(duì)象的形式,所以不需要序列化,而外部存儲(chǔ)是字節(jié)類型,所以需要序列化和反序列化。序列化一般需要分析對(duì)象的結(jié)構(gòu),解析對(duì)象的結(jié)構(gòu)會(huì)帶來很大的CPU消耗,所以一般的序列化(比如fastJson)會(huì)緩存對(duì)象解析的對(duì)象結(jié)構(gòu),以減少CPU消耗。這里沒有列出具體的序列化性能比較,但是可以通過單擊鏈接來查看。
6.2 命中率一般來說,緩存命中率越高,使用緩存的好處越高,應(yīng)用的性能越好(響應(yīng)時(shí)間越短,吞吐量越高),抗并發(fā)能力越強(qiáng)。那么影響緩存命中率的因素是什么呢?
業(yè)務(wù)場(chǎng)景和業(yè)務(wù)需求
緩存適合“重復(fù)讀較多”的業(yè)務(wù)場(chǎng)景,反之,使用緩存的意義其實(shí)并不大,命中率會(huì)很低;業(yè)務(wù)需求決定了對(duì)時(shí)效性的要求,直接影響到緩存的過期時(shí)間和更新策略。時(shí)效性要求越低,就越適合緩存。在相同 key 和相同請(qǐng)求數(shù)的情況下,緩存時(shí)間越長(zhǎng),命中率會(huì)越高;互聯(lián)網(wǎng)應(yīng)用的大多數(shù)業(yè)務(wù)場(chǎng)景下都是很適合使用緩存的。緩存的設(shè)計(jì)粒度和策略
通常情況下,緩存的粒度越小,命中率會(huì)越高;當(dāng)緩存單個(gè)對(duì)象的時(shí)候(例如:?jiǎn)蝹€(gè)用戶信息),只有當(dāng)該對(duì)象對(duì)應(yīng)的數(shù)據(jù)發(fā)生變化時(shí),我們才需要更新緩存或者讓移除緩存。而當(dāng)緩存一個(gè)集合的時(shí)候(例如:所有用戶數(shù)據(jù)),其中任何一個(gè)對(duì)象對(duì)應(yīng)的數(shù)據(jù)發(fā)生變化時(shí),都需要移除緩存(也可以直接更新)。假設(shè)其他地方也需要獲取該對(duì)象對(duì)應(yīng)的數(shù)據(jù)時(shí)(比如其他地方也需要獲取單個(gè)用戶信息),如果緩存的是單個(gè)對(duì)象,則可以直接命中緩存,反之,則可能無(wú)法直接命中;緩存的更新/過期策略也直接影響到緩存的命中率;一般有如下幾種方式:(1)固定的失效時(shí)間和被動(dòng)失效;
(2)感知數(shù)據(jù)變化,主動(dòng)更新;
(3)感知數(shù)據(jù)變化并主動(dòng)更新。并設(shè)置失效時(shí)間為被動(dòng)失效;
(4)根據(jù)數(shù)據(jù)的冷熱特性制定策略,如熱數(shù)據(jù)主動(dòng)失效重裝、冷數(shù)據(jù)失效不重裝等。
但是當(dāng)數(shù)據(jù)發(fā)生變化時(shí),直接更新緩存的命中率要高于移除緩存(或者讓緩存過期)的命中率,當(dāng)然系統(tǒng)復(fù)雜度也會(huì)更高。
緩存容量和基礎(chǔ)架構(gòu)
有限的緩存容量很容易導(dǎo)致緩存失效和過時(shí)(目前大多數(shù)緩存框架或中間件都采用LRU算法)。同時(shí),緩存技術(shù)的選擇也很重要。例如,使用內(nèi)置本地緩存更容易出現(xiàn)獨(dú)立瓶頸,而使用分布式緩存更容易擴(kuò)展。所以需要規(guī)劃系統(tǒng)容量,考慮是否可以擴(kuò)展。另外,不同的緩存框架或者中間件的效率和穩(wěn)定性也是不同的。
其他因素
緩存失效的處理:緩存節(jié)點(diǎn)失效時(shí),需要避免緩存失效,將影響降到最低。業(yè)內(nèi)的典型方式是通過一致的哈希算法或節(jié)點(diǎn)冗余。
從上面可以看出,為了提高緩存的效益,應(yīng)用程序需要盡可能直接通過緩存獲取數(shù)據(jù),避免緩存失效。業(yè)務(wù)需求、緩存粒度、緩存策略、技術(shù)選擇等方面都要綜合考慮和權(quán)衡。盡可能關(guān)注訪問頻率高、時(shí)效性要求低的熱點(diǎn)業(yè)務(wù),通過緩存預(yù)加載(預(yù)熱)、增加存儲(chǔ)容量、調(diào)整緩存粒度、更新緩存等手段提高命中率。
6.3 緩存清空策略通過上面的介紹,我們知道緩存策略對(duì)緩存的性能有很大的影響。那么,緩存策略要解決哪些問題,有哪些選擇呢?
面臨的問題
主存容量遠(yuǎn)大于CPU緩存,磁盤容量遠(yuǎn)大于主存,所以無(wú)論哪一級(jí)緩存都面臨著同樣的問題:當(dāng)有限容量緩存的空 idle 空周期全部用完,需要向緩存中添加新的內(nèi)容時(shí),如何選擇并丟棄部分原始內(nèi)容,從而騰出空周期來放這些新內(nèi)容?
解決辦法
有幾種算法可以解決這個(gè)問題,如最少使用算法(LRU)、先進(jìn)先出算法(先進(jìn)先出)、最近最少使用算法(LFU)、非最近使用算法(NMRU)等。這些算法在不同級(jí)別的緩存上執(zhí)行時(shí)效率和成本不同,需要根據(jù)具體情況選擇最合適的。以下是每種算法的簡(jiǎn)要介紹:
FIFO(first in first out)先進(jìn)先出策略,最先進(jìn)入緩存的數(shù)據(jù)在緩存空間不夠的情況下(超出最大元素限制)會(huì)被優(yōu)先被清除掉,以騰出新的空間接受新的數(shù)據(jù)。策略算法主要比較緩存元素的創(chuàng)建時(shí)間。在數(shù)據(jù)實(shí)效性要求場(chǎng)景下可選擇該類策略,優(yōu)先保障最新數(shù)據(jù)可用。LFU(less frequently used)最少使用策略,這個(gè)緩存算法使用一個(gè)計(jì)數(shù)器來記錄條目被訪問的頻率。通過使用LFU緩存算法,最低訪問數(shù)的條目首先被移除。這個(gè)方法并不經(jīng)常使用,因?yàn)樗鼰o(wú)法對(duì)一個(gè)擁有最初高訪問率之后長(zhǎng)時(shí)間沒有被訪問的條目緩存負(fù)責(zé)。策略算法主要比較元素的hitCount(命中次數(shù))。在保證高頻數(shù)據(jù)有效性場(chǎng)景下,可選擇這類策略。LRU(least recently used)最近最少使用策略,這個(gè)緩存算法將最近使用的條目存放到靠近緩存頂部的位置。當(dāng)一個(gè)新條目被訪問時(shí),LRU 將它放置到緩存的頂部。當(dāng)緩存達(dá)到極限時(shí),較早之前訪問的條目將從緩存底部開始被移除。這里會(huì)使用到昂貴的算法,而且它需要記錄“年齡位”來精確顯示條目是何時(shí)被訪問的。此外,當(dāng)一個(gè) LRU 緩存算法刪除某個(gè)條目后,“年齡位”將隨其他條目發(fā)生改變。策略算法主要比較元素最近一次被 get 使用時(shí)間。在熱點(diǎn)數(shù)據(jù)場(chǎng)景下較適用,優(yōu)先保證熱點(diǎn)數(shù)據(jù)的有效性。自適應(yīng)緩存替換算法(ARC):在 IBM Almaden 研究中心開發(fā),這個(gè)緩存算法同時(shí)跟蹤記錄 LFU 和 LRU,以及驅(qū)逐緩存條目,來獲得可用緩存的最佳使用。最近最常使用算法(MRU):這個(gè)緩存算法最先移除最近最常使用的條目。一個(gè) MRU 算法擅長(zhǎng)處理一個(gè)條目越久,越容易被訪問的情況。七、高并發(fā)場(chǎng)景常見緩存問題一般來說,在緩存時(shí)間和密鑰相同的情況下,并發(fā)性越高,緩存的好處就越高,即使緩存時(shí)間很短。然而,在高并發(fā)應(yīng)用程序場(chǎng)景中,通常會(huì)出現(xiàn)以下三個(gè)常見問題。
7.1 緩存穿透問題問題描述
場(chǎng)景:查詢某個(gè)不存在的數(shù)據(jù)。因?yàn)榫彺嫖疵袝r(shí)是被動(dòng)寫入的,而且為了容錯(cuò),如果從存儲(chǔ)層找不到數(shù)據(jù),就不會(huì)寫入緩存,這樣會(huì)導(dǎo)致不存在的數(shù)據(jù)每次被請(qǐng)求時(shí)都會(huì)在存儲(chǔ)層被查詢,從而失去緩存的意義。流量大的時(shí)候,DB可能會(huì)掛。如果有人頻繁使用不存在的密鑰攻擊我們的應(yīng)用,這就是一個(gè)漏洞。
解決辦法
方法 1. 在封裝的緩存 SET 和 GET 部分增加個(gè)步驟,如果查詢一個(gè) KEY 不存在,就以這個(gè) KEY 為前綴設(shè)定一個(gè)標(biāo)識(shí) KEY;以后再查詢?cè)?KEY 的時(shí)候,先查詢標(biāo)識(shí) KEY,如果標(biāo)識(shí) KEY 存在,就返回一個(gè)協(xié)定好的值(如:&&),然后 APP 做相應(yīng)的處理(如:檢查 KEY 是否合法,是否需要查詢 DB,是否需要設(shè)置緩存等),這樣緩存層就不會(huì)被穿透。當(dāng)然這個(gè)驗(yàn)證 KEY 的失效時(shí)間不能太長(zhǎng)。方法 2. 如果一個(gè)查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障),我們?nèi)匀话堰@個(gè)空結(jié)果進(jìn)行緩存,但它的過期時(shí)間會(huì)很短,一般只有幾分鐘。方法 3. 采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的 bitmap 中,一個(gè)一定不存在的數(shù)據(jù)會(huì)被這個(gè) bitmap 攔截掉,從而避免了對(duì)底層存儲(chǔ)系統(tǒng)的查詢壓力。7.2 緩存并發(fā)問題問題描述
有時(shí)候,如果一個(gè)網(wǎng)站有高并發(fā)訪問,如果一個(gè)緩存失敗,可能會(huì)發(fā)生多個(gè)進(jìn)程同時(shí)查詢DB,同時(shí)設(shè)置緩存的情況。如果并發(fā)量真的很大,也可能造成DB壓力過大,緩存更新頻繁。
解決辦法
可以鎖定緩存查詢,如果KEY不存在,鎖定,然后將DB檢入緩存,然后解鎖;其他進(jìn)程找到鎖就等,解鎖后返回?cái)?shù)據(jù)或者進(jìn)入DB查詢。
7.3 緩存失效問題問題描述
這個(gè)問題的主要原因是高并發(fā)。通常,當(dāng)我們?cè)O(shè)置緩存的到期時(shí)間時(shí),其中一些可能會(huì)設(shè)置為1分鐘和5分鐘。當(dāng)并發(fā)性較高時(shí),可能會(huì)同時(shí)生成多個(gè)緩存,到期時(shí)間也相同。此時(shí)可能會(huì)造成到期時(shí)間到期時(shí),這些緩存會(huì)同時(shí)失效,所有請(qǐng)求都會(huì)轉(zhuǎn)發(fā)給DB,DB可能壓力過大。
解決辦法
一個(gè)簡(jiǎn)單的方案是分散緩存到期時(shí)間。比如我們可以在原有失效時(shí)間的基礎(chǔ)上增加一個(gè)隨機(jī)值,比如1-5分鐘隨機(jī),這樣每個(gè)緩存失效時(shí)間的重復(fù)率都會(huì)降低,很難造成集體失效事件。
總結(jié)至此,我們已經(jīng)介紹完了緩存的內(nèi)容。相信本文可以幫助我們了解緩存的基本工作原理和常見緩存問題的解決方案。
1.《緩存 緩存技術(shù)原理淺析》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識(shí),僅代表作者本人觀點(diǎn),與本網(wǎng)站無(wú)關(guān),侵刪請(qǐng)聯(lián)系頁(yè)腳下方聯(lián)系方式。
2.《緩存 緩存技術(shù)原理淺析》僅供讀者參考,本網(wǎng)站未對(duì)該內(nèi)容進(jìn)行證實(shí),對(duì)其原創(chuàng)性、真實(shí)性、完整性、及時(shí)性不作任何保證。
3.文章轉(zhuǎn)載時(shí)請(qǐng)保留本站內(nèi)容來源地址,http://f99ss.com/junshi/649426.html