Author: wenchu.cenwc
Email: wenchu.cenwc@alibaba-inc.com
Memcached 介紹與分析
Memcached是一種集中式Cache,支持分布式橫向擴(kuò)展。總結(jié)幾個(gè)它的特點(diǎn)來理解一下它的優(yōu)點(diǎn)和限制。
Memory:內(nèi)存存儲(chǔ),不言而喻,速度快,對(duì)于內(nèi)存的要求高,不指出的話所緩存的內(nèi)容非持久化。對(duì)于CPU要求很低,所以常常采用將Memcached服務(wù)端和一些CPU高消耗Memory低消耗應(yīng)用部屬在一起。(作為我們AEP正好有這樣的環(huán)境,我們的接口服務(wù)器有多臺(tái),接口服務(wù)器對(duì)于CPU要求很高(由于WS-Security),但是對(duì)于Memory要求很低,因此可以用作Memcached的服務(wù)端部屬機(jī)器)
集中式Cache:避開了分布式Cache的傳播問題,但是需要非單點(diǎn)保證其可靠性,這個(gè)就是后面集成中所作的cluster的工作,可以將多個(gè)Memcached作為一個(gè)虛擬的cluster,同時(shí)對(duì)于cluster的讀寫和普通的memcached的讀寫性能沒有差別。
分布式擴(kuò)展:Memcached的很突出一個(gè)優(yōu)點(diǎn),就是采用了可分布式擴(kuò)展的模式。可以將部屬在一臺(tái)機(jī)器上的多個(gè)Memcached服務(wù)端或者部署在多個(gè)機(jī)器上的Memcached服務(wù)端組成一個(gè)虛擬的服務(wù)端,對(duì)于調(diào)用者來說完全屏蔽和透明。提高的單機(jī)器的內(nèi)存利用率,也提供了scale out的方式。
Socket通信:傳輸內(nèi)容的大小以及序列化的問題需要注意,雖然Memcached通常會(huì)被放置到內(nèi)網(wǎng)作為Cache,Socket傳輸速率應(yīng)該比較高(當(dāng)前支持Tcp和udp兩種模式,同時(shí)根據(jù)客戶端的不同可以選擇使用nio的同步或者異步調(diào)用方式),但是序列化成本和帶寬成本還是需要注意。這里也提一下序列化,對(duì)于對(duì)象序列化的性能往往讓大家頭痛,但是如果對(duì)于同一類的Class對(duì)象序列化傳輸,第一次序列化時(shí)間比較長,后續(xù)就會(huì)優(yōu)化,其實(shí)也就是說序列化最大的消耗不是對(duì)象序列化,而是類的序列化。如果穿過去的只是字符串,那么是最好的,省去了序列化的操作,因此在Memcached中保存的往往是較小的內(nèi)容。
特殊的內(nèi)存分配機(jī)制:首先要說明的是Memcached支持最大的存儲(chǔ)對(duì)象為1M。它的內(nèi)存分配比較特殊,但是這樣的分配方式其實(shí)也是對(duì)于性能考慮的,簡單的分配機(jī)制可以更容易回收再分配,節(jié)省對(duì)于CPU的使用。這里用一個(gè)酒窖比喻來說明這種內(nèi)存分配機(jī)制,首先在Memcached起來的時(shí)候可以通過參數(shù)設(shè)置使用的總共的Memory,這個(gè)就是建造一個(gè)酒窖,然后在有酒進(jìn)入的時(shí)候,首先申請(qǐng)(通常是1M)的空間,用來建酒架,酒架根據(jù)這個(gè)酒瓶的大小分割酒架為多個(gè)小格子安放酒瓶,將同樣大小范圍內(nèi)的酒瓶都放置在一類酒架上面。例如20cm半徑的酒瓶放置在可以容納20-25cm的酒架A上,30cm半徑的酒瓶就放置在容納25-30cm的酒架B上。回收機(jī)制也很簡單,首先新酒入庫,看看酒架是否有可以回收的地方,如果有直接使用,如果沒有申請(qǐng)新的地方,如果申請(qǐng)不到,采用配置的過期策略。這個(gè)特點(diǎn)來看,如果要放的內(nèi)容大小十分離散,同時(shí)大小比例相差梯度很明顯,那么可能對(duì)于使用空間來說不好,可能在酒架A上就放了一瓶酒,但占用掉了一個(gè)酒架的位置。
Cache機(jī)制簡單:有時(shí)候很多開源的項(xiàng)目做的面面俱到,但是最后也就是因?yàn)檫^于注重一些非必要性的功能而拖累了性能,這里要提到的就是Memcached的簡單性。首先它沒有什么同步,消息分發(fā),兩階段提交等等,它就是一個(gè)很簡單的Cache,把東西放進(jìn)去,然后可以取出來,如果發(fā)現(xiàn)所提供的Key沒有命中,那么就很直白的告訴你,你這個(gè)key沒有任何對(duì)應(yīng)的東西在緩存里,去數(shù)據(jù)庫或者其他地方取,當(dāng)你在外部數(shù)據(jù)源取到的時(shí)候,可以直接將內(nèi)容置入到Cache中,這樣下次就可以命中了。這里會(huì)提到怎么去同步這些數(shù)據(jù),兩種方式,一種就是在你修改了以后立刻更新Cache內(nèi)容,這樣就會(huì)即時(shí)生效。另一種是說容許有失效時(shí)間,到了失效時(shí)間,自然就會(huì)將內(nèi)容刪除,此時(shí)再去去的時(shí)候就會(huì)命中不了,然后再次將內(nèi)容置入Cache,用來更新內(nèi)容。后者用在一些時(shí)時(shí)性要求不高,寫入不頻繁的情況。
客戶端的重要性:Memcached是用C寫的一個(gè)服務(wù)端,客戶端沒有規(guī)定,反正是Socket傳輸,只要語言支持Socket通信,通過Command的簡單協(xié)議就可以通信,但是客戶端設(shè)計(jì)的合理十分重要,同時(shí)也給使用者提供了很大的空間去擴(kuò)展和設(shè)計(jì)客戶端來滿足各種場景的需要,包括容錯(cuò),權(quán)重,效率,特殊的功能性需求,嵌入框架等等。
幾個(gè)應(yīng)用點(diǎn):小對(duì)象的緩存(用戶的token,權(quán)限信息,資源信息)。小的靜態(tài)資源緩存。Sql結(jié)果的緩存(這部分用的好,性能提高相當(dāng)大,同時(shí)由于Memcached自身提供scale out,那么對(duì)于db scale out的老大難問題無疑是一劑好藥)。ESB消息緩存。
集成設(shè)計(jì)
為什么需要集成?直接使用現(xiàn)有的兩個(gè)Java實(shí)現(xiàn)Memcached是否就可以了?
當(dāng)前集成主要為了兩方面考慮,首先是方便的配置使用,如何將Memcached內(nèi)嵌到類似于ASF以及其他框架中去,并且通過配置文件方便使用,這就需要作部分的集成工作,這部分工作主要是定義了配置文件以及通過Stax去解析配置的功能。然后是如何管理Memcached,這部分內(nèi)容包括了初始化,運(yùn)行期檢測,資源回收的工作。最后是擴(kuò)展,這里的擴(kuò)展分成兩部分(功能的擴(kuò)展以及框架實(shí)現(xiàn)的擴(kuò)展),功能擴(kuò)展例如當(dāng)前擴(kuò)展了虛擬的cluster,可以讓多個(gè)memcached Client組成一個(gè)虛擬的cluster,如果通過放入cluster的方式放入到其中一個(gè)Cache Client中的話,那么就可以在整個(gè)cluster都作好備份,這樣其實(shí)可以根據(jù)memcached的單機(jī)多實(shí)例以及多機(jī)多實(shí)例作交互備份,提高可靠性。當(dāng)然后續(xù)還有很多可以擴(kuò)展的內(nèi)容,這里只是一個(gè)開頭。框架實(shí)現(xiàn)的擴(kuò)展指的是這里采用了類似于Jdk的JAXP的框架設(shè)計(jì),只是規(guī)定了框架API結(jié)構(gòu),至于實(shí)現(xiàn)者動(dòng)態(tài)載入,這個(gè)和ASF等現(xiàn)在可擴(kuò)展的框架一樣,提供了很方便的擴(kuò)展點(diǎn),后續(xù)的設(shè)計(jì)中會(huì)提到。
接口設(shè)計(jì)類圖:

圖1 Cache接口包類圖
ICache和IMemcachedCache實(shí)現(xiàn)的是最基本的Cache的功能,只是IMemcachedCache有所增強(qiáng),提供了對(duì)于虛擬的Cluster的操作,批量操作,統(tǒng)計(jì)的功能。ICacheManager和IMemcachedCacheManager分別是對(duì)于上面兩個(gè)Cache的管理類,根據(jù)配置文件解析,初始化客戶端池,建立虛擬集群,銷毀客戶端池等工作。
圖2 Memcached 實(shí)現(xiàn)包
省略了一些輔助類定義。這部分是具體的實(shí)現(xiàn),同時(shí)可以在圖上看到spi包內(nèi)的CacheManagerFactory就是用來提供擴(kuò)展使用的接口。只需要定義在jar的META-INF下面建立services目錄,建立兩個(gè)名為:com.alisoft.xplatform.asf.cache.IMemcachedCacheManager和com.alisoft.xplatform.asf.cache.spi.CacheManagerFactory的文件就可以替換MemcachedCacheManager和CacheManagerFactory的實(shí)現(xiàn)類,從而改變Memcached Client實(shí)現(xiàn)機(jī)制。如果沒有這兩個(gè)文件在Classpath目錄下面,那么默認(rèn)將會(huì)使用當(dāng)前框架中的兩個(gè)實(shí)現(xiàn)。
圖3 Memcached的結(jié)構(gòu)圖
Memcached Server就是部署在不同服務(wù)器或者在同一臺(tái)服務(wù)器上的Memcached實(shí)例,一般采用后臺(tái)守護(hù)進(jìn)程方式運(yùn)行。SocketPool是客戶端連接到服務(wù)端的Socket通信層,Memcached Client可以歸屬為虛擬的Cluster,MemcachedCacheManager作用是管理Cluster和Cache。從這個(gè)結(jié)構(gòu)圖可以看出客戶端的每一層都是很獨(dú)立,這樣有利于層次的交互,以及組合擴(kuò)展。
測試與使用
1. 配置:
需要有一個(gè)名為memcached.xml的文件在classpath中,可以在jar里面也可以在任意classpath可以找的到的地方,需要注意的是,CacheManager實(shí)現(xiàn)了對(duì)于多個(gè)memcached.xml merge的功能。
具體的配置內(nèi)容如下:
<?xml version="1.0" encoding="UTF-8"?>
<memcached>//總標(biāo)簽
//memcached Client的配置,也就是一個(gè)IMemcachedCache的配置。Name必須填,表示Cache的名稱,socketpool必須填,表示使用的遠(yuǎn)程通信連接池是哪一個(gè),參看后面對(duì)于socketpool的定義。后面都是可選的,第三個(gè)參數(shù)表示傳輸?shù)臅r(shí)候是否壓縮,第四個(gè)參數(shù)表示默認(rèn)的編碼方式
<client name="mclient1" socketpool="pool1" compressEnable="true" defaultEncoding="UTF-8" >
<!--errorHandler></errorHandler-->//可定義錯(cuò)誤處理類,一般不需要定義
</client>
<client name="mclient2" socketpool="pool2" compressEnable="true" defaultEncoding="UTF-8" >
</client>
//socketpool是通信連接池定義,每一個(gè)memcached Client要和服務(wù)端交互都必須有通信連接池作為底層數(shù)據(jù)通信的支持,name必填,表示名字,同時(shí)也是memcached client指定socketpool的依據(jù),failover表示對(duì)于服務(wù)器出現(xiàn)問題時(shí)的自動(dòng)修復(fù)。initConn初始的時(shí)候連接數(shù),minConn表示最小閑置連接數(shù),maxConn最大連接數(shù),maintSleep表示是否需要延時(shí)結(jié)束(最好設(shè)置為0,如果設(shè)置延時(shí)的話那么就不能夠立刻回收所有的資源,如果此時(shí)從新啟動(dòng)同樣的資源分配,就會(huì)出現(xiàn)問題),nagle是TCP對(duì)于socket創(chuàng)建的算法,socketTO是socket連接超時(shí)時(shí)間,aliveCheck表示心跳檢查,確定服務(wù)器的狀態(tài)。Servers是memcached服務(wù)端開的地址和ip列表字符串,weights是上面服務(wù)器的權(quán)重,必須數(shù)量一致,否則權(quán)重?zé)o效
<socketpool name="pool1" failover="true" initConn="10" minConn="5" maxConn="250" maintSleep="0"
nagle="false" socketTO="3000" aliveCheck="true">
<servers>10.0.68.210:12000,10.0.68.210:12222</servers>
<weights>5,5</weights>
</socketpool>
<socketpool name="pool2" failover="true" initConn="10" minConn="5" maxConn="250" maintSleep="0"
nagle="false" socketTO="3000" aliveCheck="true">
<servers>10.0.68.210:22000,10.0.68.210:22222</servers>
<weights>5,5</weights>
</socketpool>
//虛擬集群設(shè)置,這里將幾個(gè)client的cache設(shè)置為一個(gè)虛擬集群,當(dāng)對(duì)這些IMemcachedCache作集群操作的時(shí)候,就會(huì)自動(dòng)地對(duì)集群中所有的Cache作插入,尋找以及刪除的操作,做一個(gè)虛擬交互備份
<cluster name="cluster1">
<memCachedClients>mclient1,mclient2</memCachedClients>
</cluster>
</memcached>
2. 測試代碼,這里就附帶一個(gè)單元測試類的代碼就可以很清楚的知道使用方法。
后話
沒有不好的,只有不適合的,適合的場景使用,根據(jù)場景適合的使用,才是提高性能的最有效手段。后需要根據(jù)所需的應(yīng)用場景繼續(xù)對(duì)這部分集成內(nèi)容作完善,實(shí)踐完善設(shè)計(jì)。