【轉】Ehcache詳細解讀

Ehcache 是如今最流行的純Java開源緩存框架,配置簡單、結構清晰、功能強大,最初知道它,是從Hibernate的緩存開始的。網上中文的EhCache材料 以簡單介紹和配置方法居多,若是你有這方面的問題,請自行google;對於API,官網上介紹已經很是清楚,請參見官網;可是不多見到特性說明和對實現 原理的分析,所以在這篇文章裏面,我會詳細介紹和分析EhCache的特性,加上一些本身的理解和思考,但願對緩存感興趣的朋友有所收穫。java

 

1、特性一覽,來自官網,簡單翻譯一下:程序員

 

一、快速輕量
過去幾年,諸多測試代表Ehcache是最快的Java緩存之一。
Ehcache的線程機制是爲大型高併發系統設計的。
大量性能測試用例保證Ehcache在不一樣版本間性能表現得一致性。
不少用戶都不知道他們正在用Ehcache,由於不須要什麼特別的配置。
API易於使用,這就很容易部署上線和運行。
很小的jar包,Ehcache 2.2.3才668kb。
最小的依賴:惟一的依賴就是SLF4J了。

二、伸縮性
緩存在內存和磁盤存儲能夠伸縮到數G,Ehcache爲大數據存儲作過優化。
大內存的狀況下,全部進程能夠支持數百G的吞吐。
爲高併發和大型多CPU服務器作優化。
線程安全和性能老是一對矛盾,Ehcache的線程機制設計採用了Doug Lea的想法來得到較高的性能。
單臺虛擬機上支持多緩存管理器。
經過Terracotta服務器矩陣,能夠伸縮到數百個節點。

三、靈活性
Ehcache 1.2具有對象API接口和可序列化API接口。
不能序列化的對象可使用除磁盤存儲外Ehcache的全部功能。
除了元素的返回方法之外,API都是統一的。只有這兩個方法不一致:getObjectValue和getKeyValue。這就使得緩存對象、序列化對象來獲取新的特性這個過程很簡單。
支持基於Cache和基於Element的過時策略,每一個Cache的存活時間都是能夠設置和控制的。
提供了LRU、LFU和FIFO緩存淘汰算法,Ehcache 1.2引入了最少使用和先進先出緩存淘汰算法,構成了完整的緩存淘汰算法。
提供內存和磁盤存儲,Ehcache和大多數緩存解決方案同樣,提供高性能的內存和磁盤存儲。
動態、運行時緩存配置,存活時間、空閒時間、內存和磁盤存放緩存的最大數目都是能夠在運行時修改的。

四、標準支持
Ehcache提供了對JSR107 JCACHE API最完整的實現。由於JCACHE在發佈之前,Ehcache的實現(如net.sf.jsr107cache)已經發布了。
實現JCACHE API有利於到將來其餘緩存解決方案的可移植性。
Ehcache的維護者Greg Luck,正是JSR107的專家委員會委員。

五、可擴展性
監聽器能夠插件化。Ehcache 1.2提供了CacheManagerEventListener和CacheEventListener接口,實現能夠插件化,而且能夠在ehcache.xml裏配置。
節點發現,冗餘器和監聽器均可以插件化。
分佈式緩存,從Ehcache 1.2開始引入,包含了一些權衡的選項。Ehcache的團隊相信沒有什麼是萬能的配置。
實現者可使用內建的機制或者徹底本身實現,由於有完整的插件開發指南。
緩存的可擴展性能夠插件化。建立你本身的緩存擴展,它能夠持有一個緩存的引用,而且綁定在緩存的生命週期內。
緩存加載器能夠插件化。建立你本身的緩存加載器,可使用一些異步方法來加載數據到緩存裏面。
緩存異常處理器能夠插件化。建立一個異常處理器,在異常發生的時候,能夠執行某些特定操做。

六、應用持久化
在VM重啓後,持久化到磁盤的存儲能夠復原數據。
Ehcache是第一個引入緩存數據持久化存儲的開源Java緩存框架。緩存的數據能夠在機器重啓後從磁盤上從新得到。
根據須要將緩存刷到磁盤。將緩存條目刷到磁盤的操做能夠經過cache.flush()方法來執行,這大大方便了Ehcache的使用。

七、監聽器
緩存管理器監聽器。容許註冊實現了CacheManagerEventListener接口的監聽器:
notifyCacheAdded()
notifyCacheRemoved()
緩存事件監聽器。容許註冊實現了CacheEventListener接口的監聽器,它提供了許多對緩存事件發生後的處理機制:
notifyElementRemoved/Put/Updated/Expired 

八、開啓JMX
Ehcache的JMX功能是默認開啓的,你能夠監控和管理以下的MBean:
CacheManager、Cache、CacheConfiguration、CacheStatistics 

九、分佈式緩存
從Ehcache 1.2開始,支持高性能的分佈式緩存,兼具靈活性和擴展性。
分佈式緩存的選項包括:
經過Terracotta的緩存集羣:設定和使用Terracotta模式的Ehcache緩存。緩存發現是自動完成的,而且有不少選項能夠用來調試緩存行爲和性能。
使用RMI、JGroups或者JMS來冗餘緩存數據:節點能夠經過多播或發現者手動配置。狀態更新能夠經過RMI鏈接來異步或者同步完成。
Custom:一個綜合的插件機制,支持發現和複製的能力。
可用的緩存複製選項。支持的經過RMI、JGroups或JMS進行的異步或同步的緩存複製。
可靠的分發:使用TCP的內建分發機制。
節點發現:節點能夠手動配置或者使用多播自動發現,而且能夠自動添加和移除節點。對於多播阻塞的狀況下,手動配置能夠很好地控制。
分佈式緩存能夠任意時間加入或者離開集羣。緩存能夠配置在初始化的時候執行引導程序員。
BootstrapCacheLoaderFactory抽象工廠,實現了BootstrapCacheLoader接口(RMI實現)。
緩存服務端。Ehcache提供了一個Cache Server,一個war包,爲絕大多數web容器或者是獨立的服務器提供支持。
緩存服務端有兩組API:面向資源的RESTful,還有就是SOAP。客戶端沒有實現語言的限制。
RESTful緩存服務器:Ehcached的實現嚴格遵循RESTful面向資源的架構風格。
SOAP緩存服務端:Ehcache RESTFul Web Services API暴露了單例的CacheManager,他能在ehcache.xml或者IoC容器裏面配置。
標準服務端包含了內嵌的Glassfish web容器。它被打成了war包,能夠任意部署到支持Servlet 2.5的web容器內。Glassfish V2/三、Tomcat 6和Jetty 6都已經通過了測試。

十、搜索
標準分佈式搜索使用了流式查詢接口的方式,請參閱文檔。

十一、Java EE和應用緩存
爲普通緩存場景和模式提供高質量的實現。
阻塞緩存:它的機制避免了複製進程併發操做的問題。
SelfPopulatingCache在緩存一些開銷昂貴操做時顯得特別有用,它是一種針對讀優化的緩存。它不須要調用者知道緩存元素怎樣被返回,也支持在不阻塞讀的狀況下刷新緩存條目。
CachingFilter:一個抽象、可擴展的cache filter。
SimplePageCachingFilter:用於緩存基於request URI和Query String的頁面。它能夠根據HTTP request header的值來選擇採用或者不採用gzip壓縮方式將頁面發到瀏覽器端。你能夠用它來緩存整個Servlet頁面,不管你採用的是JSP、 velocity,或者其餘的頁面渲染技術。
SimplePageFragmentCachingFilter:緩存頁面片斷,基於request URI和Query String。在JSP中使用jsp:include標籤包含。
已經使用Orion和Tomcat測試過,兼容Servlet 2.三、Servlet 2.4規範。
Cacheable命令:這是一種老的命令行模式,支持異步行爲、容錯。
兼容Hibernate,兼容Google App Engine。
基於JTA的事務支持,支持事務資源管理,二階段提交和回滾,以及本地事務。

十二、開源協議
Apache 2.0 licenseweb

 

2、Ehcache的加載模塊列表,他們都是獨立的庫,每一個都爲Ehcache添加新的功能,能夠在此下載 :算法

 

  • ehcache-core:API,標準緩存引擎,RMI複製和Hibernate支持
  • ehcache:分佈式Ehcache,包括Ehcache的核心和Terracotta的庫
  • ehcache-monitor:企業級監控和管理
  • ehcache-web:爲Java Servlet Container提供緩存、gzip壓縮支持的filters
  • ehcache-jcache:JSR107 JCACHE的實現
  • ehcache-jgroupsreplication:使用JGroup的複製
  • ehcache-jmsreplication:使用JMS的複製
  • ehcache-openjpa:OpenJPA插件
  • ehcache-server:war內部署或者單獨部署的RESTful cache server
  • ehcache-unlockedreadsview:容許Terracotta cache的無鎖讀
  • ehcache-debugger:記錄RMI分佈式調用事件
  • Ehcache for Ruby:Jruby and Rails支持

Ehcache的結構設計概覽:數據庫

3、核心定義編程

 

cache manager:緩存管理器,之前是隻容許單例的,不過如今也能夠多實例了api

cache:緩存管理器內能夠放置若干cache,存放數據的實質,全部cache都實現了Ehcache接口數組

element:單條緩存數據的組成單位瀏覽器

system of record(SOR):能夠取到真實數據的組件,能夠是真正的業務邏輯、外部接口調用、存放真實數據的數據庫等等,緩存就是從SOR中讀取或者寫入到SOR中去的。緩存

 

代碼示例:

Java代碼   收藏代碼
CacheManager manager = CacheManager.newInstance("src/config/ehcache.xml");  
manager.addCache("testCache");  
Cache test = singletonManager.getCache("testCache");  
test.put(new Element("key1", "value1"));  
manager.shutdown();  

  

固然,也支持這種相似DSL的配置方式,配置都是能夠在運行時動態修改的:

Java代碼   收藏代碼
Cache testCache = new Cache(  
  new CacheConfiguration("testCache", maxElements)  
    .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)  
    .overflowToDisk(true)  
    .eternal(false)  
    .timeToLiveSeconds(60)  
    .timeToIdleSeconds(30)  
    .diskPersistent(false)  
    .diskExpiryThreadIntervalSeconds(0));  

  

事務的例子:

Java代碼   收藏代碼
Ehcache cache = cacheManager.getEhcache("xaCache");  
transactionManager.begin();  
try {  
    Element e = cache.get(key);  
    Object result = complexService.doStuff(element.getValue());  
    cache.put(new Element(key, result));  
    complexService.doMoreStuff(result);  
    transactionManager.commit();  
} catch (Exception e) {  
    transactionManager.rollback();  
}  

  

 

4、一致性模型

 

說到一致性,數據庫的一致性是怎樣的?不妨先來回顧一下數據庫的幾個隔離級別:

未提交讀(Read Uncommitted):在讀數據時不會檢查或使用任何鎖。所以,在這種隔離級別中可能讀取到沒有提交的數據。會出現髒讀、不可重複讀、幻象讀。
已提交讀(Read Committed):只讀取提交的數據並等待其餘事務釋放排他鎖。讀數據的共享鎖在讀操做完成後當即釋放。已提交讀是數據庫的默認隔離級別。會出現不可重複讀、幻象讀。
可重複讀(Repeatable Read):像已提交讀級別那樣讀數據,但會保持共享鎖直到事務結束。會出現幻象讀。
可序列化(Serializable):工做方式相似於可重複讀。但它不只會鎖定受影響的數據,還會鎖定這個範圍,這就阻止了新數據插入查詢所涉及的範圍。

 

基於以上,再來對比思考下面的一致性模型:

 

一、強一致性模型:系統中的某個數據被成功更新(事務成功返回)後,後續任何對該數據的讀取操做都獲得更新後的值。這是傳統關係數據庫提供的一致性模型,也是關係數據庫深受人們喜好的緣由之一。強一致性模型下的性能消耗一般是最大的。

 

二、弱一致性模型:系統中的某個數據被更新後,後續對該數據的讀取操做獲得的不必定是更新後的值,這種狀況下一般有個「不一致性時間窗口」存在:即數據更新完成後在通過這個時間窗口,後續讀取操做就可以獲得更新後的值。

 

三、最終一致性模型:屬於弱一致性的一種,即某個數據被更新後,若是該數據後續沒有被再次更新,那麼最終全部的讀取操做都會返回更新後的值。

 

最終一致性模型包含以下幾個必要屬性,都比較好理解:

 

  • 讀寫一致:某線程A,更新某條數據之後,後續的訪問所有都能取得更新後的數據。
  • 會話內一致:它本質上和上面那一條是一致的,某用戶更改了數據,只要會話還存在,後續他取得的全部數據都必須是更改後的數據。
  • 單調讀一致:若是一個進程能夠看到當前的值,那麼後續的訪問不能返回以前的值。
  • 單調寫一致:對同一進程內的寫行爲必須是保序的,不然,寫完畢的結果就是不可預期的了。

四、Bulk Load:這種模型是基於批量加載數據到緩存裏面的場景而優化的,沒有引入鎖和常規的淘汰算法這些下降性能的東西,它和最終一致性模型很像,可是有批量、高速寫和弱一致性保證的機制。

 

這樣幾個API也會影響到一致性的結果:

 

一、顯式鎖(Explicit Locking ):若是咱們自己就配置爲強一致性,那麼天然全部的緩存操做都具有事務性質。而若是咱們配置成最終一致性時,再在外部使用顯式鎖API,也能夠達到事務的效果。固然這樣的鎖能夠控制得更細粒度,可是依然可能存在競爭和線程阻塞。

 

二、無鎖可讀取視圖(UnlockedReadsView):一個容許髒讀的decorator,它只能用在強一致性的配置下,它經過申請一個特殊的寫鎖來比徹底的強一致性配置提高性能。

舉例以下,xml配置爲強一致性模型:

Xml代碼   收藏代碼
  1. <cache name="myCache"  
  2.      maxElementsInMemory="500"  
  3.      eternal="false"  
  4.      overflowToDisk="false"  
  5.    <terracotta clustered="true" consistency="strong" />  
  6. </cache>  

可是使用UnlockedReadsView:

Java代碼   收藏代碼
  1. Cache cache = cacheManager.getEhcache("myCache");  
  2. UnlockedReadsView unlockedReadsView = new UnlockedReadsView(cache, "myUnlockedCache");  

 

三、原子方法(Atomic methods):方法執行是原子化 的,即CAS操做(Compare and Swap)。CAS最終也實現了強一致性的效果,但不一樣的是,它是採用樂觀鎖而不是悲觀鎖來實現的。在樂觀鎖機制下,更新的操做可能不成功,由於在這過程 中可能會有其餘線程對同一條數據進行變動,那麼在失敗後須要從新執行更新操做。現代的CPU都支持CAS原語了。

Java代碼   收藏代碼
  1. cache.putIfAbsent(Element element);  
  2. cache.replace(Element oldOne, Element newOne);  
  3. cache.remove(Element);  

 

5、緩存拓撲類型

 

一、獨立緩存(Standalone Ehcache):這樣的緩存應用節點都是獨立的,互相不通訊。

 

二、分佈式緩存(Distributed Ehcache):數據存儲在Terracotta的服務器陣列(Terracotta Server Array,TSA)中,可是最近使用的數據,能夠存儲在各個應用節點中。

 

邏輯視角:


L1緩存就在各個應用節點上,而L2緩存則放在Cache Server陣列中。

 

組網視角:

 

模型存儲視角:


L1級緩存是沒有持久化存儲的。另外,從緩存數據量上看,server端遠大於應用節點。

 

三、複製式緩存(Replicated Ehcache):緩存數據時同時存放在多個應用節點的,數據複製和失效的事件以同步或者異步的形式在各個集羣節點間傳播。上述事件到來時,會阻塞寫線程的操做。在這種模式下,只有弱一致性模型。

 

它有以下幾種事件傳播機制:RMI、JGroups、JMS和Cache Server。

 

RMI模式下,全部節點所有對等:

 

JGroup模式:能夠配置單播或者多播,協議棧和配置都很是靈活。

 

Xml代碼   收藏代碼
  1. <cacheManagerPeerProviderFactory  
  2. class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory"  
  3. properties="connect=UDP(mcast_addr=231.12.21.132;mcast_port=45566;):PING:  
  4. MERGE2:FD_SOCK:VERIFY_SUSPECT:pbcast.NAKACK:UNICAST:pbcast.STABLE:FRAG:pbcast.GMS"  
  5. propertySeparator="::"  
  6. />  

 

 

JMS模式:這種模式的核心就是一個消息隊列,每一個應用節點都訂閱預先定義好的主題,同時,節點有元素更新時,也會發布更新元素到主題中去。JMS規範實現者上,Open MQ和Active MQ這兩個,Ehcache的兼容性都已經測試過。

Cache Server模式:這種模式下存在主從節點,通訊能夠經過RESTful的API或者SOAP。

不管上面哪一個模式,更新事件又能夠分爲updateViaCopy或updateViaInvalidate,後者只是發送一個過時消息,效率要高得多。

複製式緩存容易出現數據不一致的問題,若是這成爲一個問題,能夠考慮使用數據同步分發的機制。

 

即使不採用分佈式緩存和複製式緩存,依然會出現一些很差的行爲,好比:

 

緩存漂移(Cache Drift):每一個應用節點只管理本身的緩存,在更新某個節點的時候,不會影響到其餘的節點,這樣數據之間可能就不一樣步了。這在web會話數據緩存中狀況尤甚。

 

數據庫瓶頸(Database Bottlenecks ):對於單實例的應用來講,緩存能夠保護數據庫的讀風暴;可是,在集羣的環境下,每個應用節點都要按期保持數據最新,節點越多,要維持這樣的狀況對數據庫的開銷也越大。

 

6、存儲方式

 

一、堆內存儲:速度快,可是容量有限。

 

二、堆外(OffHeapStore)存儲:被稱爲 BigMemory,只在企業版本的Ehcache中提供,原理是利用nio的DirectByteBuffers實現,比存儲到磁盤上快,並且徹底不受 GC的影響,能夠保證響應時間的穩定性;可是direct buffer的在分配上的開銷要比heap buffer大,並且要求必須以字節數組方式存儲,所以對象必須在存儲過程當中進行序列化,讀取則進行反序列化操做,它的速度大約比堆內存儲慢一個數量級。

(注:direct buffer不受GC影響,可是direct buffer歸屬的的JAVA對象是在堆上且可以被GC回收的,一旦它被回收,JVM將釋放direct buffer的堆外空間。)

 

三、磁盤存儲。

 

7、緩存使用模式

 

cache-aside:直接操做。先詢問cache某條緩存數據是否存在,存在的話直接從cache中返回數據,繞過SOR;若是不存在,從SOR中取得數據,而後再放入cache中。

 

Java代碼   收藏代碼
  1. public V readSomeData(K key)   
  2. {  
  3.    Element element;  
  4.    if ((element = cache.get(key)) != null) {  
  5.        return element.getValue();  
  6.    }  
  7.    if (value = readDataFromDataStore(key)) != null) {  
  8.        cache.put(new Element(key, value));  
  9.    }   
  10.    return value;  
  11. }  

 

cache-as-sor:結合了read-through、write-through或write-behind操做,經過給SOR增長了一層代理,對外部應用訪問來講,它不用區別數據是從緩存中仍是從SOR中取得的。

read-through。

write-through。

write-behind(write-back):既將寫的過程變爲異步的,又進一步延遲寫入數據的過程。

 

 

Copy Cache的兩個模式:CopyOnRead和CopyOnWrite。

CopyOnRead指的是在讀緩存數據的請求到達時,若是發現數據已通過期,須要從新從源處獲取,發起的copy element的操做(pull);

CopyOnWrite則是發生在真實數據寫入緩存時,發起的更新其餘節點的copy element的操做(push)。

 

前者適合在不容許多個線程訪問同一個element的時候使用,後者則容許你自由控制緩存更新通知的時機。

更多push和pull的變化和不一樣,也可參見這裏

 

8、多種配置方式

 

包括配置文件、聲明式配置、編程式配置,甚至經過指定構造器的參數來完成配置,配置設計的原則包括:

全部配置要放到一塊兒

緩存的配置能夠很容易在開發階段、運行時修改

錯誤的配置可以在程序啓動時發現,在運行時修改出錯則須要拋出運行時異常

提供默認配置,幾乎全部的配置都是可選的,都有默認值

 

9、自動資源控制(Automatic Resource Control,ARC):

 

它是提供了一種智能途徑來控制緩存,調優性能。特性包括:

內存內緩存對象大小的控制,避免OOM出現

池化(cache manager級別)的緩存大小獲取,避免單獨計算緩存大小的消耗

靈活的獨立基於層的大小計算能力,下圖中能夠看到,不一樣層的大小都是能夠單獨控制的

能夠統計字節大小、緩存條目數和百分比

優化高命中數據的獲取,以提高性能,參見下面對緩存數據在不一樣層之間的流轉的介紹

緩存數據的流轉包括了這樣幾種行爲:

Flush:緩存條目向低層次移動。

Fault:從低層拷貝一個對象到高層。在獲取緩存的過程當中,某一層發現本身的該緩存條目已經失效,就觸發了Fault行爲。

Eviction:把緩存條目除去。

Expiration:失效狀態。

Pinning:強制緩存條目保持在某一層。

下面的圖反映了數據在各個層之間的流轉,也反映了數據的生命週期:

 

10、監控功能

 

監控的拓撲:


每一個應用節點部署一個監控探針,經過TCP協議與監控服務器聯繫,最終將數據提供給富文本客戶端或者監控操做服務器。

 

11、廣域網複製

緩存數據複製方面,Ehcache容許兩個地理位置各異的節點在廣域網下維持數據一致性,同時它提供了這樣幾種方案(注:下面的示例都只繪製了兩個節點的情形,實際能夠推廣到N個節點):

 

第一種方案:Terracotta Active/Mirror Replication。


這種方案下,服務端包含一個活躍節點,一個備份節點;各個應用節點所有靠該活躍節點提供讀寫服務。這種方式最簡單,管理容易;可是,須要寄但願於理想的網絡情況,服務器之間和客戶端到服務器之間都存在走WAN的狀況,這樣的方案其實最不穩定。

 

第二種方案:Transactional Cache Manager Replication。


這 種方案下,數據讀取不須要通過WAN,寫入數據時寫入兩份,分別由兩個cache manager處理,一份在本地Server,一份到其餘Server去。這種方案下讀的吞吐量較高並且延遲較低;可是須要引入一個XA事務管理器,兩個 cache manager寫兩份數據致使寫開銷較大,並且過WAN的寫延遲依然可能致使系統響應的瓶頸。

 

第三種方案:Messaging based (AMQ) replication。


這 種方案下,引入了批量處理和隊列,用以減緩WAN的瓶頸出現,同時,把處理讀請求和複製邏輯從Server Array物理上就剝離開,避免了WAN狀況惡化對節點讀取業務的影響。這種方案要較高的吞吐量和較低的延遲,讀/複製的分離保證了能夠提供完備的消息分 發保證、衝突處理等特性;可是它較爲複雜,並且還須要一個消息總線。

 

有一些Ehcache特性應用較少或者比較邊緣化,沒有提到,例如對於JMX的支持;還有一些則是有相似的特性和介紹了,例如對於WEB的支持,請參見我這篇關於OSCache的解讀,其中的「web支持」一節有詳細的原理分析。

 

最後,關於Ehcache的性能比對,下面這張圖來自Ehcache的創始人Greg Luck的blog

 

put/get上Ehcache要500-1000倍快過Memcached。緣由何在?他本身分析道:「In-process caching and asynchronous replication are a clear performance winner」。有關它詳細的內容仍是請參閱他的blog吧。

 

轉自:

http://raychase.iteye.com/blog/1545906
相關文章
相關標籤/搜索