導(dǎo)語(yǔ):在網(wǎng)絡(luò)分層應(yīng)用服務(wù)中,緩存的使用已比較普及,,本文將結(jié)合作者實(shí)際工作經(jīng)驗(yàn)總結(jié),,講述在不同的場(chǎng)景下如何選擇和使用適用的緩存框架,以達(dá)到提升服務(wù)質(zhì)量,,優(yōu)化系統(tǒng)架構(gòu)的目的,。 一般而言,現(xiàn)在互聯(lián)網(wǎng)模式(一個(gè)網(wǎng)站或一個(gè)應(yīng)用),,整體流程可以概括描述為 瀏覽器→應(yīng)用服務(wù)器→數(shù)據(jù)庫(kù)或文件(存儲(chǔ))→應(yīng)用服務(wù)器→瀏覽器,,這是一個(gè)標(biāo)準(zhǔn)流程,通過(guò)瀏覽器(或App界面)發(fā)起請(qǐng)求,,經(jīng)過(guò)服務(wù)器,、數(shù)據(jù)庫(kù)計(jì)算整合后反饋瀏覽器呈現(xiàn)內(nèi)容,。隨著互聯(lián)網(wǎng)的普及,內(nèi)容信息越來(lái)越復(fù)雜,,使用者和訪問(wèn)量越來(lái)越大,,我們的應(yīng)用需要支撐更多的并發(fā)量,同時(shí)我們的應(yīng)用服務(wù)器和數(shù)據(jù)庫(kù)服務(wù)器所做的計(jì)算也越來(lái)越多,。但是往往我們的應(yīng)用服務(wù)器資源是有限的,,且技術(shù)變革是緩慢的,數(shù)據(jù)庫(kù)每秒能接受的請(qǐng)求次數(shù)也是有限的(或者文件的讀寫也是有限的),,如何能夠有效利用有限的資源來(lái)提供盡可能大的吞吐量,?一個(gè)有效的辦法就是減少計(jì)算量,縮短請(qǐng)求流程——這就是緩存,。緩存的出現(xiàn)就是打破上述的標(biāo)準(zhǔn)流程,,其中的任何一個(gè)環(huán)節(jié)都可以被截?cái)啵?qǐng)求可以從緩存中直接獲取目標(biāo)數(shù)據(jù)并返回,。通過(guò)這種打破常規(guī)的方式,,有效減少計(jì)算量,縮短請(qǐng)求流程,,有效提升響應(yīng)速度,,節(jié)省硬件資源,讓有限的資源服務(wù)更多的用戶,。 如圖1所示,,緩存的使用可以出現(xiàn)在 1-4的各個(gè)環(huán)節(jié)中,每個(gè)環(huán)節(jié)的緩存方案與使用各有特點(diǎn),。 圖1 網(wǎng)絡(luò)應(yīng)用一般流程 緩存特征 根據(jù)面向?qū)ο蟮能浖季S來(lái)看,,緩存就是一個(gè)對(duì)象類型,那么必然有它的屬性: 命中率 命中率=返回正確結(jié)果數(shù)/請(qǐng)求緩存次數(shù),,命中率問(wèn)題是緩存中的一個(gè)非常重要的問(wèn)題,,它是衡量緩存有效性的重要指標(biāo)。命中率越高,,表明緩存的使用率越高,。 最大元素(或最大空間) 緩存中可以存放的最大元素的數(shù)量,一旦緩存中元素?cái)?shù)量超過(guò)這個(gè)值(或者緩存數(shù)據(jù)所占空間超過(guò)其最大支持空間),,那么將會(huì)觸發(fā)緩存啟動(dòng)清空策略根據(jù)不同的場(chǎng)景合理的設(shè)置最大元素值往往可以一定程度上提高緩存的命中率,,從而更有效的時(shí)候緩存。 清空策略 如上描述,,緩存的存儲(chǔ)空間有限制,,當(dāng)緩存空間被用滿時(shí),,如何保證在穩(wěn)定服務(wù)的同時(shí)有效提升命中率,?這就由緩存清空策略來(lái)處理,,設(shè)計(jì)適合自身數(shù)據(jù)特征的情況策略能有效提升命中率。常見(jiàn)的一般策略有: a. FIFO(first in first out) 先進(jìn)先出策略,,最先進(jìn)入緩存的數(shù)據(jù)在緩存空間不夠的情況下(超出最大元素限制)會(huì)被優(yōu)先被清除掉,,以騰出新的空間接受新的數(shù)據(jù)。策略算法主要比較緩存元素的創(chuàng)建時(shí)間,。 b. LFU(less frequently used) 最少使用策略,,無(wú)論是否過(guò)期,根據(jù)元素的被使用次數(shù)判斷,,清除使用次數(shù)較少的元素釋放空間,。策略算法主要比較元素的hitCount(命中次數(shù))。 c. LRU(least recently used) 最近最少使用策略,,無(wú)論是否過(guò)期,,根據(jù)元素最后一次被使用的時(shí)間戳,清除最遠(yuǎn)使用時(shí)間戳的元素釋放空間,。策略算法主要比較元素最近一次被get使用時(shí)間,。 除此之外,還有一些簡(jiǎn)單策略比如: 根據(jù)過(guò)期時(shí)間判斷,,清理過(guò)期時(shí)間最長(zhǎng)的元素,; 根據(jù)過(guò)期時(shí)間判斷,清理最近要過(guò)期的元素,; 隨機(jī)清理,; 根據(jù)關(guān)鍵字(或元素內(nèi)容)長(zhǎng)短清理等。 緩存介質(zhì) (從硬件介質(zhì)上來(lái)看,,無(wú)非就是內(nèi)存和硬盤兩種)從技術(shù)上劃分,,可以分成幾種,內(nèi)存,、硬盤文件,、數(shù)據(jù)庫(kù)。
在目前的應(yīng)用服務(wù)框架中,,我們對(duì)緩存的分類劃分更常用的是根據(jù)緩存與應(yīng)用的耦合程度,,劃分為local cache(本地緩存)和remote cache(分布式緩存):
目前各種類型的緩存都活躍在成千上萬(wàn)的應(yīng)用服務(wù)中,還沒(méi)有一種緩存方案可以解決一切的業(yè)務(wù)場(chǎng)景或數(shù)據(jù)類型,,我們需要根據(jù)自身的特殊場(chǎng)景和背景,,選擇最適合的緩存方案。緩存的使用是程序員,、架構(gòu)師的必備技能,,好的程序員能根據(jù)數(shù)據(jù)類型、業(yè)務(wù)場(chǎng)景來(lái)準(zhǔn)確判斷使用何種類型的緩存,,如何使用這種緩存,,以最小的成本最快的效率達(dá)到最優(yōu)的目的。 本地緩存編程式緩存實(shí)現(xiàn) a. 成員變量或局部變量實(shí)現(xiàn) 簡(jiǎn)單代碼示例如圖2所示,。 圖2 簡(jiǎn)單代碼示例圖 以局部變量map結(jié)構(gòu)緩存部分業(yè)務(wù)數(shù)據(jù),,減少頻繁的重復(fù)數(shù)據(jù)庫(kù)I/O操作,。缺點(diǎn)僅限于類的自身作用域內(nèi),類間無(wú)法共享緩存,。 b. 靜態(tài)變量實(shí)現(xiàn) 最常用的單例實(shí)現(xiàn)靜態(tài)資源緩存,,代碼示例如下: public class CityUtils { 業(yè)務(wù)中常用的城市基礎(chǔ)基本信息判斷,通過(guò)靜態(tài)變量一次獲取緩存內(nèi)存中,,減少頻繁的I/O讀取,靜態(tài)變量實(shí)現(xiàn)類間可共享,,進(jìn)程內(nèi)可共享,,緩存的實(shí)時(shí)性稍差。 為了解決本地緩存數(shù)據(jù)的實(shí)時(shí)性問(wèn)題,,目前大量使用的是結(jié)合ZooKeeper的自動(dòng)發(fā)現(xiàn)機(jī)制,,實(shí)時(shí)變更本地靜態(tài)變量緩存: MtConfig基礎(chǔ)組件,采用的就是類似原理,,使用靜態(tài)變量緩存,,結(jié)合ZooKeeper的統(tǒng)一管理,做到自動(dòng)動(dòng)態(tài)更新緩存,,如圖3所示,。 圖3 Mtconfig實(shí)現(xiàn)圖 這類的緩存實(shí)現(xiàn),優(yōu)點(diǎn)是直接的在heap區(qū)內(nèi)讀寫,,最快也最方便,;缺點(diǎn)同樣是受heap區(qū)域影響,緩存的數(shù)據(jù)量非常有限,,同時(shí)緩存時(shí)間受GC影響,。主要滿足單機(jī)場(chǎng)景下的小數(shù)據(jù)量緩存需求,同時(shí)對(duì)緩存數(shù)據(jù)的變更無(wú)需太敏感感知,,如上一般配置管理,、基礎(chǔ)靜態(tài)數(shù)據(jù)等場(chǎng)景。 EhCache Ehcache是現(xiàn)在最流行的純Java開(kāi)源緩存框架,,配置簡(jiǎn)單,、結(jié)構(gòu)清晰、功能強(qiáng)大,,是一個(gè)非常輕量級(jí)的緩存實(shí)現(xiàn),,我們常用的Hibernate里面就集成了相關(guān)緩存功能。 圖4 ehcache框架圖 從圖4中我們可以了解到,,ehcache的核心定義主要包括: cache manager:緩存管理器,,以前是只允許單例的,不過(guò)現(xiàn)在也可以多實(shí)例了,。 cache:緩存管理器內(nèi)可以放置若干cache,,存放數(shù)據(jù)的實(shí)質(zhì),,所有cache都實(shí)現(xiàn)了Ehcache接口,這是一個(gè)真正使用的緩存實(shí)例,;通過(guò)緩存管理器的模式,,可以在單個(gè)應(yīng)用中輕松隔離多個(gè)緩存實(shí)例,獨(dú)立服務(wù)于不同業(yè)務(wù)場(chǎng)景需求,,緩存數(shù)據(jù)物理隔離,,同時(shí)需要時(shí)又可共享使用。 element:?jiǎn)螚l緩存數(shù)據(jù)的組成單位,。 system of record(SOR):可以取到真實(shí)數(shù)據(jù)的組件,,可以是真正的業(yè)務(wù)邏輯、外部接口調(diào)用,、存放真實(shí)數(shù)據(jù)的數(shù)據(jù)庫(kù)等,,緩存就是從SOR中讀取或者寫入到SOR中去的。 在上層可以看到,,整個(gè)ehcache提供了對(duì) JSR,、JMX等的標(biāo)準(zhǔn)支持,能夠較好的兼容和移植,,同時(shí)對(duì)各類對(duì)象有較完善的監(jiān)控管理機(jī)制,。它的緩存介質(zhì)涵蓋堆內(nèi)存(heap)、堆外內(nèi)存(BigMemory 商用版本支持)和磁盤,,各介質(zhì)可獨(dú)立設(shè)置屬性和策略,。ehcache最初是獨(dú)立的本地緩存框架組件,在后期的發(fā)展中,,結(jié)合Terracotta服務(wù)陣列模型,,可以支持分布式緩存集群,主要有RMI,、JGroups,、JMS和Cache Server等傳播方式進(jìn)行節(jié)點(diǎn)間通信,如圖4的左側(cè)部分描述,。 整體數(shù)據(jù)流轉(zhuǎn)包括這樣幾類行為:
圖5反應(yīng)了數(shù)據(jù)在各個(gè)層之間的流轉(zhuǎn),,同時(shí)也體現(xiàn)了各層數(shù)據(jù)的一個(gè)生命周期,。 以下看下ehcache的配置使用:
整體上看,,ehcache的使用還是相對(duì)簡(jiǎn)單便捷的,提供了完整的各類API接口,。需要注意的是,,雖然ehcache支持磁盤的持久化,但是由于存在兩級(jí)緩存介質(zhì),,在一級(jí)內(nèi)存中的緩存,,如果沒(méi)有主動(dòng)的刷入磁盤持久化的話,在應(yīng)用異常down機(jī)等情形下,,依然會(huì)有緩存數(shù)據(jù)丟失的出現(xiàn),,為此可以根據(jù)需要將緩存刷到磁盤,將緩存條目刷到磁盤的操作可以通過(guò)cache.flush方法來(lái)執(zhí)行,,需要注意的是,對(duì)于對(duì)象的磁盤寫入,,前提是要將對(duì)象進(jìn)行序列化,。 主要特性:
Guava Cache Guava Cache是google開(kāi)源的java重用工具集庫(kù)里的一款緩存工具,,其主要實(shí)現(xiàn)的緩存功能有:
圖5 guavacache數(shù)據(jù)結(jié)構(gòu)圖 Guava Cache的架構(gòu)設(shè)計(jì)來(lái)源于ConcurrentHashMap的靈感,,我們前面也提到過(guò),簡(jiǎn)單場(chǎng)景下可以自行編碼通過(guò)hashmap來(lái)做少量數(shù)據(jù)的緩存,,但是如果結(jié)果可能隨時(shí)間改變或者是希望存儲(chǔ)的數(shù)據(jù)空間可控的話,,自己實(shí)現(xiàn)這種數(shù)據(jù)結(jié)構(gòu)還是有必要的。 Guava Cache繼承了ConcurrentHashMap的思路,,使用多個(gè)segments方式的細(xì)粒度鎖,,在保證線程安全的同時(shí),支持高并發(fā)場(chǎng)景需求,。Cache類似于Map,,它是存儲(chǔ)鍵值對(duì)的集合,不同的是它還需要處理evict,、expire,、dynamic load等算法邏輯,需要一些額外信息來(lái)實(shí)現(xiàn)這些操作,。對(duì)此,,根據(jù)面向?qū)ο笏枷耄枰龇椒ㄅc數(shù)據(jù)的關(guān)聯(lián)封裝,。如圖6示cache的內(nèi)存數(shù)據(jù)模型,,可以看到,使用ReferenceEntry接口來(lái)封裝一個(gè)鍵值對(duì),,而用ValueReference來(lái)封裝Value值,,之所以用Reference命令,是因?yàn)镃ache要支持WeakReference Key和SoftReference,、WeakReference value,。 圖6 緩存數(shù)據(jù)流轉(zhuǎn)圖 ReferenceEntry是對(duì)一個(gè)鍵值對(duì)節(jié)點(diǎn)的抽象,它包含了key和值的ValueReference抽象類,,Cache由多個(gè)Segment組成,,而每個(gè)Segment包含一個(gè)ReferenceEntry數(shù)組,,每個(gè)ReferenceEntry數(shù)組項(xiàng)都是一條ReferenceEntry鏈,且一個(gè)ReferenceEntry包含key,、hash,、valueReference、next字段,。除了在ReferenceEntry數(shù)組項(xiàng)中組成的鏈,,在一個(gè)Segment中,所有ReferenceEntry還組成access鏈(accessQueue)和write鏈(writeQueue)(后面會(huì)介紹鏈的作用),。ReferenceEntry可以是強(qiáng)引用類型的key,,也可以WeakReference類型的key,為了減少內(nèi)存使用量,,還可以根據(jù)是否配置了expireAfterWrite,、expireAfterAccess、maximumSize來(lái)決定是否需要write鏈和access鏈確定要?jiǎng)?chuàng)建的具體Reference:StrongEntry,、StrongWriteEntry,、StrongAccessEntry、StrongWriteAccessEntry等,。 對(duì)于ValueReference,,因?yàn)镃ache支持強(qiáng)引用的Value,、SoftReference Value以及WeakReference Value,因而它對(duì)應(yīng)三個(gè)實(shí)現(xiàn)類:StrongValueReference,、SoftValueReference,、WeakValueReference,。為了支持動(dòng)態(tài)加載機(jī)制,它還有一個(gè)LoadingValueReference,,在需要?jiǎng)討B(tài)加載一個(gè)key的值時(shí),,先把該值封裝在LoadingValueReference中,以表達(dá)該key對(duì)應(yīng)的值已經(jīng)在加載了,,如果其他線程也要查詢?cè)搆ey對(duì)應(yīng)的值,,就能得到該引用,并且等待改值加載完成,,從而保證該值只被加載一次,,在該值加載完成后,將LoadingValueReference替換成其他ValueReference類型,。ValueReference對(duì)象中會(huì)保留對(duì)ReferenceEntry的引用,,這是因?yàn)樵赩alue因?yàn)閃eakReference、SoftReference被回收時(shí),,需要使用其key將對(duì)應(yīng)的項(xiàng)從Segment的table中移除,。 WriteQueue和AccessQueue:為了實(shí)現(xiàn)最近最少使用算法,,Guava Cache在Segment中添加了兩條鏈:write鏈(writeQueue)和access鏈(accessQueue),這兩條鏈都是一個(gè)雙向鏈表,,通過(guò)ReferenceEntry中的previousInWriteQueue,、nextInWriteQueue和previousInAccessQueue、nextInAccessQueue鏈接而成,,但是以Queue的形式表達(dá),。WriteQueue和AccessQueue都是自定義了offer、add(直接調(diào)用offer),、remove,、poll等操作的邏輯,對(duì)offer(add)操作,,如果是新加的節(jié)點(diǎn),,則直接加入到該鏈的結(jié)尾,如果是已存在的節(jié)點(diǎn),,則將該節(jié)點(diǎn)鏈接的鏈尾,;對(duì)remove操作,直接從該鏈中移除該節(jié)點(diǎn),;對(duì)poll操作,,將頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)移除,并返回,。 了解了cache的整體數(shù)據(jù)結(jié)構(gòu)后,,再來(lái)看下針對(duì)緩存的相關(guān)操作就簡(jiǎn)單多了: Segment中的evict清除策略操作,是在每一次調(diào)用操作的開(kāi)始和結(jié)束時(shí)觸發(fā)清理工作,,這樣比一般的緩存另起線程監(jiān)控清理相比,,可以減少開(kāi)銷,但如果長(zhǎng)時(shí)間沒(méi)有調(diào)用方法的話,,會(huì)導(dǎo)致不能及時(shí)的清理釋放內(nèi)存空間的問(wèn)題,。evict主要處理四個(gè)Queue:1. keyReferenceQueue;2. valueReferenceQueue,;3. writeQueue,;4. accessQueue。前兩個(gè)queue是因?yàn)閃eakReference,、SoftReference被垃圾回收時(shí)加入的,,清理時(shí)只需要遍歷整個(gè)queue,將對(duì)應(yīng)的項(xiàng)從LocalCache中移除即可,,這里keyReferenceQueue存放ReferenceEntry,,而valueReferenceQueue存放的是ValueReference,要從Cache中移除需要有key,因而ValueReference需要有對(duì)ReferenceEntry的引用,,這個(gè)前面也提到過(guò)了,。而對(duì)后面兩個(gè)Queue,只需要檢查是否配置了相應(yīng)的expire時(shí)間,,然后從頭開(kāi)始查找已經(jīng)expire的Entry,,將它們移除即可。 Segment中的put操作:put操作相對(duì)比較簡(jiǎn)單,,首先它需要獲得鎖,,然后嘗試做一些清理工作,接下來(lái)的邏輯類似ConcurrentHashMap中的rehash,,查找位置并注入數(shù)據(jù),。需要說(shuō)明的是當(dāng)找到一個(gè)已存在的Entry時(shí),需要先判斷當(dāng)前的ValueRefernece中的值事實(shí)上已經(jīng)被回收了,,因?yàn)樗鼈兛梢允荳eakReference,、SoftReference類型,如果已經(jīng)被回收了,,則將新值寫入,。并且在每次更新時(shí)注冊(cè)當(dāng)前操作引起的移除事件,指定相應(yīng)的原因:COLLECTED,、REPLACED等,,這些注冊(cè)的事件在退出的時(shí)候統(tǒng)一調(diào)用Cache注冊(cè)的RemovalListener,由于事件處理可能會(huì)有很長(zhǎng)時(shí)間,,因而這里將事件處理的邏輯在退出鎖以后才做,。最后,在更新已存在的Entry結(jié)束后都嘗試著將那些已經(jīng)expire的Entry移除,。另外put操作中還需要更新writeQueue和accessQueue的語(yǔ)義正確性,。 Segment帶CacheLoader的get操作:1. 先查找table中是否已存在沒(méi)有被回收,、也沒(méi)有expire的entry,,如果找到,并在CacheBuilder中配置了refreshAfterWrite,,并且當(dāng)前時(shí)間間隔已經(jīng)操作這個(gè)事件,,則重新加載值,否則,,直接返回原有的值,;2. 如果查找到的ValueReference是LoadingValueReference,則等待該LoadingValueReference加載結(jié)束,,并返回加載的值,;3. 如果沒(méi)有找到entry,或者找到的entry的值為,,則加鎖后,,繼續(xù)在table中查找已存在key對(duì)應(yīng)的entry,,如果找到并且對(duì)應(yīng)的entry.isLoading為true,則表示有另一個(gè)線程正在加載,,因而等待那個(gè)線程加載完成,,如果找到一個(gè)非值,返回該值,,否則創(chuàng)建一個(gè)LoadingValueReference,,并調(diào)用loadSync加載相應(yīng)的值,在加載完成后,,將新加載的值更新到table中,,即大部分情況下替換原來(lái)的LoadingValueReference。 Guava Cache提供 Builder模式的CacheBuilder生成器來(lái)創(chuàng)建緩存的方式,,十分方便,,并且各個(gè)緩存參數(shù)的配置設(shè)置,類似于函數(shù)式編程的寫法,,可自行設(shè)置各類參數(shù)選型,。 它提供三種方式加載 到緩存中。分別是:
圖7 memcached內(nèi)存結(jié)構(gòu)圖 使用粗暴直接的方式,,直接Cache.put 加載數(shù)據(jù),但自動(dòng)加載是首選的,,因?yàn)樗梢愿菀椎耐茢嗨芯彺鎯?nèi)容的一致性,。 build生成器的兩種方式都實(shí)現(xiàn)了一種邏輯:從緩存中取key的值,如果該值已經(jīng)緩存過(guò)了則返回緩存中的值,,如果沒(méi)有緩存過(guò)可以通過(guò)某個(gè)方法來(lái)獲取這個(gè)值,,不同的地方在于cacheloader的定義比較寬泛,是針對(duì)整個(gè)cache定義的,,可以認(rèn)為是統(tǒng)一的根據(jù)key值load value的方法,,而callable的方式較為靈活,允許你在get的時(shí)候指定 load方法,。使用示例如下: /** * CacheLoader */ public void loadingCache { LoadingCache 總體來(lái)看,,Guava Cache基于ConcurrentHashMap的優(yōu)秀設(shè)計(jì)借鑒,在高并發(fā)場(chǎng)景支持和線程安全上都有相應(yīng)的改進(jìn)策略,,使用Reference引用命令,,提升高并發(fā)下的數(shù)據(jù)……訪問(wèn)速度并保持了GC的可回收,有效節(jié)省空間;同時(shí),,write鏈和access鏈的設(shè)計(jì),,能更靈活、高效的實(shí)現(xiàn)多種類型的緩存清理策略,,包括基于容量的清理,、基于時(shí)間的清理、基于引用的清理等,;編程式的build生成器管理,,讓使用者有更多的自由度,能夠根據(jù)不同場(chǎng)景設(shè)置合適的模式,。 分布式緩存memcached緩存 memcached是應(yīng)用較廣的開(kāi)源remove cache產(chǎn)品之一,,它本身其實(shí)不提供分布式的解決方案的。在服務(wù)端,,memcached集群環(huán)境實(shí)際就是一個(gè)個(gè)memcached服務(wù)器的堆積,,環(huán)境搭建較為簡(jiǎn)單;cache的分布式主要是在客戶端實(shí)現(xiàn),,通過(guò)客戶端的路由處理來(lái)達(dá)到分布式解決方案的目的,。客戶端做路由的原理非常簡(jiǎn)單,,應(yīng)用服務(wù)器在每次存取某key的value時(shí),,通過(guò)某種算法把key映射到某臺(tái)memcached服務(wù)器nodeA上,因此這個(gè)key所有操作都在nodeA上,,結(jié)構(gòu)圖如圖7,、圖8所示。 memcached客戶端采用一致性hash算法作為路由策略,,如圖8,,相對(duì)于一般hash(如簡(jiǎn)單取模)的算法,一致性hash算法除了計(jì)算key的hash值外,,還會(huì)計(jì)算每個(gè)server對(duì)應(yīng)的hash值,,然后將這些hash值映射到一個(gè)有限的值域上(比如0~2^32)。通過(guò)尋找hash值大于hash(key)的最小server作為存儲(chǔ)該key數(shù)據(jù)的目標(biāo)server,。如果找不到,,則直接把具有最小hash值的server作為目標(biāo)server,。同時(shí),,一定程度上,解決了擴(kuò)容問(wèn)題,,增加或刪除單個(gè)節(jié)點(diǎn),,對(duì)于整個(gè)集群來(lái)說(shuō),不會(huì)有大的影響。最近版本,,增加了虛擬節(jié)點(diǎn)的設(shè)計(jì),,進(jìn)一步提升了可用性。 圖8 memcached客戶端路由圖 圖9 memcached一致性hash示例圖 Memcached是一個(gè)高效的分布式內(nèi)存cache,,了解memcached的內(nèi)存管理機(jī)制,,才能更好的掌握memcached,讓我們可以針對(duì)我們數(shù)據(jù)特點(diǎn)進(jìn)行調(diào)優(yōu),,讓其更好的為我所用,。我們知道m(xù)emcached僅支持基礎(chǔ)的key-value 鍵值對(duì)類型數(shù)據(jù)存儲(chǔ)。在Memcached內(nèi)存結(jié)構(gòu)中有兩個(gè)非常重要的概念:slab和chunk,。 slab是一個(gè)內(nèi)存塊,,它是memcached一次申請(qǐng)內(nèi)存的最小單位。在啟動(dòng)memcached的時(shí)候一般會(huì)使用參數(shù)-m指定其可用內(nèi)存,,但是并不是在啟動(dòng)的那一刻所有的內(nèi)存就全部分配出去了,,只有在需要的時(shí)候才會(huì)去申請(qǐng),而且每次申請(qǐng)一定是一個(gè)slab,。Slab的大小固定為1M(1048576 Byte),,一個(gè)slab由若干個(gè)大小相等的chunk組成。每個(gè)chunk中都保存了一個(gè)item結(jié)構(gòu)體,、一對(duì)key和value,。 雖然在同一個(gè)slab中chunk的大小相等的,但是在不同的slab中chunk的大小并不一定相等,,在memcached中按照chunk的大小不同,,可以把slab分為很多種類(class),默認(rèn)情況下memcached把slab分為40類(class1~class40),,在class 1中,,chunk的大小為80字節(jié),由于一個(gè)slab的大小是固定的1048576字節(jié)(1M),,因此在class1中最多可以有13107個(gè)chunk(也就是這個(gè)slab能存最多 13107個(gè)小于80字節(jié)的 key-value 數(shù)據(jù)),。 Memcached內(nèi)存管理采取預(yù)分配、分組管理的方式,,分組管理就是我們上面提到的slab class,,按照chunk的大小slab被分為很多種類。內(nèi)存預(yù)分配過(guò)程是怎樣的呢,?向memcached添加一個(gè)item時(shí)候,,memcached首先會(huì)根據(jù)item的大小,來(lái)選擇最合適的slab class:例如item的大小為190字節(jié),,默認(rèn)情況下class 4的chunk大小為160字節(jié)顯然不合適,,class 5的chunk大小為200字節(jié),,大于190字節(jié),因此該item將放在class 5中(顯然這里會(huì)有10字節(jié)的浪費(fèi)是不可避免的),,計(jì)算好所要放入的chunk之后,,memcached會(huì)去檢查該類大小的chunk還有沒(méi)有空閑的,如果沒(méi)有,,將會(huì)申請(qǐng)1M(1個(gè)slab)的空間并劃分為該種類chunk,。例如我們第一次向memcached中放入一個(gè)190字節(jié)的item時(shí),memcached會(huì)產(chǎn)生一個(gè)slab class 2(也叫一個(gè)page),,并會(huì)用去一個(gè)chunk,,剩余5241個(gè)chunk供下次有適合大小item時(shí)使用,當(dāng)我們用完這所有的5242個(gè)chunk之后,,下次再有一個(gè)在160~200字節(jié)之間的item添加進(jìn)來(lái)時(shí),,memcached會(huì)再次產(chǎn)生一個(gè)class 5的slab(這樣就存在了2個(gè)pages)。 總結(jié)來(lái)看,,memcached內(nèi)存管理需要注意的幾個(gè)方面:
對(duì)于key-value 信息,,最好不要超過(guò)1m的大小,;同時(shí)信息長(zhǎng)度最好相對(duì)是比較均衡穩(wěn)定的,,這樣能夠保障最大限度的使用內(nèi)存;同時(shí),,memcached采用的LRU清理策略,,合理甚至過(guò)期時(shí)間,提高命中率,。 無(wú)特殊場(chǎng)景下,,key-value能滿足需求的前提下,使用memcached分布式集群是較好的選擇,,搭建與操作使用都比較簡(jiǎn)單,;分布式集群在單點(diǎn)故障時(shí),只影響小部分?jǐn)?shù)據(jù)異常,,目前還可以通過(guò)Magent緩存代理模式,,做單點(diǎn)備份,,提升高可用,;整個(gè)緩存都是基于內(nèi)存的,,因此響應(yīng)時(shí)間是很快,不需要額外的序列化,、反序列化的程序,,但同時(shí)由于基于內(nèi)存,數(shù)據(jù)沒(méi)有持久化,,集群故障重啟數(shù)據(jù)無(wú)法恢復(fù),。高版本的memcached已經(jīng)支持CAS模式的原子操作,可以低成本的解決并發(fā)控制問(wèn)題,。 …… 點(diǎn)擊閱讀原文,,查看全部?jī)?nèi)容。 |
|