原文:
https://blog.csdn.net/u014209975/article/details/53320395
ehcache是現(xiàn)在最流行的純java開源框架,配置簡單秽誊,結構清晰,功能強大,最初知道它,是從hibernate的緩存開始的。網上中文的ehcache材料以簡單的介紹和配置方法居多剧劝,如果你有這方面的問題,請自行看官網api文檔边篮,但是很少見到特性說明和對實現(xiàn)原理的分析幢码,因此在這這篇文章里面笤休,我會詳細介紹和分析ehcache的特性,加上一些自己的理解和思考症副,希望對緩存感興趣的朋友有所收獲店雅。
1.快速輕量
過去幾年,眾多的測試表明ehcache是最快的java緩存之一
ehcache的線程機制是為大型高并發(fā)系統(tǒng)設計的
大量性能測試用例保證ehcache在不同版本間性能表現(xiàn)的一致性
很多用戶都不知道他們正在用ehcache贞铣,因此不需要什么特別的配置
api易于使用闹啦,這就很容易部署上線和運行
2.伸縮性
緩存在內存和硬盤存儲可以伸縮到數G,ehcache為大數據存儲做過優(yōu)化
大內存的情況下辕坝,所有進程可以支持數百G的吞吐
為高并發(fā)和大型多CPU服務器做優(yōu)化
線程安全和性能總是一些矛盾窍奋,ehcache的線程機制設計采用了Doug lea的想法來獲得較高的性能
單臺虛擬機上支持多緩存管理器
通過Terracotta服務器矩陣,可以伸縮到數百個節(jié)點
3.靈活性
ehcache具備對象api接口和可序列化api接口
不能序列化的對象可以使用出磁盤存儲外ehcache的所有功能
除了元素的返回方法以外酱畅,api都是統(tǒng)一的琳袄,只有這2個方法不一致:getObjectValue和getKeyValue。這就使得緩存對象纺酸,序列化對象來獲取新的特性這個過程簡單
支持基于Cache和基于Element的過期策略窖逗,每個Cache的存活時間都是可以設置和控制的。
提供了LRU餐蔬、LFU和FIFO緩存淘汰算法碎紊,Ehcache 1.2引入了最少使用和先進先出緩存淘汰算法,構成了完整的緩存淘汰算法樊诺。
提供內存和磁盤存儲仗考,Ehcache和大多數緩存解決方案一樣,提供高性能的內存和磁盤存儲词爬。
動態(tài)秃嗜、運行時緩存配置,存活時間、空閑時間痪寻、內存和磁盤存放緩存的最大數目都是可以在運行時修改的螺句。
4.標準支持
5.可擴展性
監(jiān)聽器可以插件化.ehcache1.2提供了cacheManageEventListener和cacheEventListener接口,實現(xiàn)了可插件化橡类。并且可以在ehcache。xml配置
節(jié)點發(fā)現(xiàn)芽唇,冗余器和監(jiān)聽器都可以插件化
6.應用持久化
在vm重啟后顾画,持久化到磁盤的存儲可以復原數據
Ehcache是第一個引入緩存數據持久化存儲的開源java緩存框架,緩存的數據可以在機器重啟后從磁盤上重新獲得
根據需要將緩存刷到磁盤匆笤。將緩存條目刷到磁盤的操作可以通過cache.fiush方法執(zhí)行,這大大方便了ehcache的使用
7研侣、監(jiān)聽器
緩存管理器監(jiān)聽器。允許注冊實現(xiàn)了CacheManagerEventListener接口的監(jiān)聽器:
notifyCacheAdded()
notifyCacheRemoved()
緩存事件監(jiān)聽器炮捧。允許注冊實現(xiàn)了CacheEventListener接口的監(jiān)聽器庶诡,它提供了許多對緩存事件發(fā)生后的處理機制:
notifyElementRemoved/Put/Updated/Expired
8、開啟JMX
Ehcache的JMX功能是默認開啟的咆课,你可以監(jiān)控和管理如下的MBean:
CacheManager末誓、Cache、CacheConfiguration书蚪、CacheStatistics
9喇澡、分布式緩存
從Ehcache 1.2開始,支持高性能的分布式緩存殊校,兼具靈活性和擴展性晴玖。
分布式緩存的選項包括:
通過Terracotta的緩存集群:設定和使用Terracotta模式的Ehcache緩存。緩存發(fā)現(xiàn)是自動完成的为流,并且有很多選項可以用來調試緩存行為和性能呕屎。
使用RMI、JGroups或者JMS來冗余緩存數據:節(jié)點可以通過多播或發(fā)現(xiàn)者手動配置敬察。狀態(tài)更新可以通過RMI連接來異步或者同步完成秀睛。
Custom:一個綜合的插件機制,支持發(fā)現(xiàn)和復制的能力静汤。
可用的緩存復制選項裳擎。支持的通過RMI察绷、JGroups或JMS進行的異步或同步的緩存復制。
可靠的分發(fā):使用TCP的內建分發(fā)機制。
節(jié)點發(fā)現(xiàn):節(jié)點可以手動配置或者使用多播自動發(fā)現(xiàn)织堂,并且可以自動添加和移除節(jié)點。對于多播阻塞的情況下溉潭,手動配置可以很好地控制倦微。
分布式緩存可以任意時間加入或者離開集群。緩存可以配置在初始化的時候執(zhí)行引導程序員药蜻。
BootstrapCacheLoaderFactory抽象工廠瓷式,實現(xiàn)了BootstrapCacheLoader接口(RMI實現(xiàn))替饿。
緩存服務端。Ehcache提供了一個Cache Server贸典,一個war包视卢,為絕大多數web容器或者是獨立的服務器提供支持。
緩存服務端有兩組API:面向資源的RESTful廊驼,還有就是SOAP据过。客戶端沒有實現(xiàn)語言的限制妒挎。
RESTful緩存服務器:Ehcached的實現(xiàn)嚴格遵循RESTful面向資源的架構風格绳锅。
SOAP緩存服務端:Ehcache RESTFul Web Services API暴露了單例的CacheManager,他能在ehcache.xml或者IoC容器里面配置酝掩。
標準服務端包含了內嵌的Glassfish web容器鳞芙。它被打成了war包,可以任意部署到支持Servlet 2.5的web容器內期虾。Glassfish V2/3原朝、Tomcat 6和Jetty 6都已經經過了測試。
10彻消、搜索
標準分布式搜索使用了流式查詢接口的方式竿拆,請參閱文檔。
11宾尚、Java EE和應用緩存
為普通緩存場景和模式提供高質量的實現(xiàn)丙笋。
阻塞緩存:它的機制避免了復制進程并發(fā)操作的問題。
SelfPopulatingCache在緩存一些開銷昂貴操作時顯得特別有用煌贴,它是一種針對讀優(yōu)化的緩存御板。它不需要調用者知道緩存元素怎樣被返回,也支持在不阻塞讀的情況下刷新緩存條目牛郑。
CachingFilter:一個抽象怠肋、可擴展的cache filter。
SimplePageCachingFilter:用于緩存基于request URI和Query String的頁面淹朋。它可以根據HTTP request header的值來選擇采用或者不采用gzip壓縮方式將頁面發(fā)到瀏覽器端笙各。你可以用它來緩存整個Servlet頁面,無論你采用的是JSP础芍、velocity杈抢,或者其他的頁面渲染技術。
SimplePageFragmentCachingFilter:緩存頁面片段仑性,基于request URI和Query String惶楼。在JSP中使用jsp:include標簽包含。
已經使用Orion和Tomcat測試過,兼容Servlet 2.3歼捐、Servlet 2.4規(guī)范何陆。
Cacheable命令:這是一種老的命令行模式,支持異步行為豹储、容錯贷盲。
兼容Hibernate,兼容Google App Engine颂翼。
基于JTA的事務支持晃洒,支持事務資源管理,二階段提交和回滾朦乏,以及本地事務。
12氧骤、開源協(xié)議
Apache 2.0 license
二呻疹、Ehcache的加載模塊列表,他們都是獨立的庫筹陵,每個都為Ehcache添加新的功能刽锤,可以在此下載 :
- ehcache-core:API,標準緩存引擎朦佩,RMI復制和Hibernate支持
- ehcache:分布式Ehcache并思,包括Ehcache的核心和Terracotta的庫
- ehcache-monitor:企業(yè)級監(jiān)控和管理
- ehcache-web:為Java Servlet Container提供緩存、gzip壓縮支持的filters
- ehcache-jcache:JSR107 JCACHE的實現(xiàn)
- 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的結構設計概覽:
三语稠、核心定義:
cache manager:緩存管理器宋彼,以前是只允許單例的,不過現(xiàn)在也可以多實例了
cache:緩存管理器內可以放置若干cache仙畦,存放數據的實質输涕,所有cache都實現(xiàn)了Ehcache接口
element:單條緩存數據的組成單位
system of record(SOR):可以取到真實數據的組件,可以是真正的業(yè)務邏輯慨畸、外部接口調用莱坎、存放真實數據的數據庫等等,緩存就是從SOR中讀取或者寫入到SOR中去的寸士。
代碼示例:
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的配置方式,配置都是可以在運行時動態(tài)修改的:
Cache testCache = new Cache(
new CacheConfiguration("testCache", maxElements)
.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
.overflowToDisk(true)
.eternal(false)
.timeToLiveSeconds(60)
.timeToIdleSeconds(30)
.diskPersistent(false)
.diskExpiryThreadIntervalSeconds(0));
事務的例子:
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();
}
四弱卡、一致性模型:
說到一致性乃正,數據庫的一致性是怎樣的?不妨先來回顧一下數據庫的幾個隔離級別:
未提交讀(Read Uncommitted):在讀數據時不會檢查或使用任何鎖谐宙。因此烫葬,在這種隔離級別中可能讀取到沒有提交的數據。會出現(xiàn)臟讀、不可重復讀搭综、幻象讀垢箕。
已提交讀(Read Committed):只讀取提交的數據并等待其他事務釋放排他鎖。讀數據的共享鎖在讀操作完成后立即釋放兑巾。已提交讀是數據庫的默認隔離級別条获。會出現(xiàn)不可重復讀、幻象讀蒋歌。
可重復讀(Repeatable Read):像已提交讀級別那樣讀數據帅掘,但會保持共享鎖直到事務結束。會出現(xiàn)幻象讀堂油。
可序列化(Serializable):工作方式類似于可重復讀修档。但它不僅會鎖定受影響的數據,還會鎖定這個范圍府框,這就阻止了新數據插入查詢所涉及的范圍吱窝。
基于以上,再來對比思考下面的一致性模型:
1迫靖、強一致性模型:系統(tǒng)中的某個數據被成功更新(事務成功返回)后院峡,后續(xù)任何對該數據的讀取操作都得到更新后的值。這是傳統(tǒng)關系數據庫提供的一致性模型系宜,也是關系數據庫深受人們喜愛的原因之一照激。強一致性模型下的性能消耗通常是最大的。
2盹牧、弱一致性模型:系統(tǒng)中的某個數據被更新后俩垃,后續(xù)對該數據的讀取操作得到的不一定是更新后的值,這種情況下通常有個“不一致性時間窗口”存在:即數據更新完成后在經過這個時間窗口欢策,后續(xù)讀取操作就能夠得到更新后的值吆寨。
3、最終一致性模型:屬于弱一致性的一種踩寇,即某個數據被更新后啄清,如果該數據后續(xù)沒有被再次更新,那么最終所有的讀取操作都會返回更新后的值俺孙。
最終一致性模型包含如下幾個必要屬性辣卒,都比較好理解:
- 讀寫一致:某線程A,更新某條數據以后睛榄,后續(xù)的訪問全部都能取得更新后的數據荣茫。
- 會話內一致:它本質上和上面那一條是一致的,某用戶更改了數據场靴,只要會話還存在啡莉,后續(xù)他取得的所有數據都必須是更改后的數據港准。
- 單調讀一致:如果一個進程可以看到當前的值,那么后續(xù)的訪問不能返回之前的值咧欣。
- 單調寫一致:對同一進程內的寫行為必須是保序的浅缸,否則,寫完畢的結果就是不可預期的了魄咕。
4衩椒、Bulk Load:這種模型是基于批量加載數據到緩存里面的場景而優(yōu)化的,沒有引入鎖和常規(guī)的淘汰算法這些降低性能的東西哮兰,它和最終一致性模型很像毛萌,但是有批量、高速寫和弱一致性保證的機制喝滞。
這樣幾個API也會影響到一致性的結果:
1阁将、顯式鎖(Explicit Locking ):如果我們本身就配置為強一致性,那么自然所有的緩存操作都具備事務性質右遭。而如果我們配置成最終一致性時冀痕,再在外部使用顯式鎖API,也可以達到事務的效果狸演。當然這樣的鎖可以控制得更細粒度,但是依然可能存在競爭和線程阻塞僻他。
2宵距、無鎖可讀取視圖(UnlockedReadsView):一個允許臟讀的decorator,它只能用在強一致性的配置下吨拗,它通過申請一個特殊的寫鎖來比完全的強一致性配置提升性能满哪。
舉例如下,xml配置為強一致性模型:
<cache name="myCache"
maxElementsInMemory="500"
eternal="false"
overflowToDisk="false"
<terracotta clustered="true" consistency="strong" />
</cache>
但是使用UnlockedReadsView:
Cache cache = cacheManager.getEhcache("myCache");
UnlockedReadsView unlockedReadsView = new UnlockedReadsView(cache, "myUnlockedCache");
3劝篷、原子方法(Atomic methods):方法執(zhí)行是原子化的哨鸭,即CAS操作(Compare and Swap)。CAS最終也實現(xiàn)了強一致性的效果娇妓,但不同的是像鸡,它是采用樂觀鎖而不是悲觀鎖來實現(xiàn)的。在樂觀鎖機制下哈恰,更新的操作可能不成功只估,因為在這過程中可能會有其他線程對同一條數據進行變更,那么在失敗后需要重新執(zhí)行更新操作∽疟粒現(xiàn)代的CPU都支持CAS原語了蛔钙。
cache.putIfAbsent(Element element);
cache.replace(Element oldOne, Element newOne);
cache.remove(Element);
五、緩存拓撲類型:
1荠医、獨立緩存(Standalone Ehcache):這樣的緩存應用節(jié)點都是獨立的吁脱,互相不通信桑涎。
2、分布式緩存(Distributed Ehcache):數據存儲在Terracotta的服務器陣列(Terracotta Server Array兼贡,TSA)中攻冷,但是最近使用的數據,可以存儲在各個應用節(jié)點中紧显。
邏輯視角:
組網視角:
模型存儲視角:
L1級緩存是沒有持久化存儲的讲衫。另外,從緩存數據量上看孵班,server端遠大于應用節(jié)點涉兽。
3、復制式緩存(Replicated Ehcache):緩存數據時同時存放在多個應用節(jié)點的篙程,數據復制和失效的事件以同步或者異步的形式在各個集群節(jié)點間傳播枷畏。上述事件到來時,會阻塞寫線程的操作虱饿。在這種模式下拥诡,只有弱一致性模型。
它有如下幾種事件傳播機制:RMI氮发、JGroups渴肉、JMS和Cache Server。
RMI模式下爽冕,所有節(jié)點全部對等:
JGroup模式:可以配置單播或者多播仇祭,協(xié)議棧和配置都非常靈活。
JMS模式:這種模式的核心就是一個消息隊列颈畸,每個應用節(jié)點都訂閱預先定義好的主題乌奇,同時,節(jié)點有元素更新時眯娱,也會發(fā)布更新元素到主題中去礁苗。JMS規(guī)范實現(xiàn)者上,Open MQ和Active MQ這兩個徙缴,Ehcache的兼容性都已經測試過试伙。
Cache Server模式:這種模式下存在主從節(jié)點,通信可以通過RESTful的API或者SOAP娜搂。
無論上面哪個模式迁霎,更新事件又可以分為updateViaCopy或updateViaInvalidate,后者只是發(fā)送一個過期消息百宇,效率要高得多考廉。
復制式緩存容易出現(xiàn)數據不一致的問題,如果這成為一個問題携御,可以考慮使用數據同步分發(fā)的機制昌粤。
即便不采用分布式緩存和復制式緩存既绕,依然會出現(xiàn)一些不好的行為,比如:
緩存漂移(Cache Drift):每個應用節(jié)點只管理自己的緩存涮坐,在更新某個節(jié)點的時候凄贩,不會影響到其他的節(jié)點,這樣數據之間可能就不同步了袱讹。這在web會話數據緩存中情況尤甚疲扎。
數據庫瓶頸(Database Bottlenecks ):對于單實例的應用來說,緩存可以保護數據庫的讀風暴捷雕;但是椒丧,在集群的環(huán)境下,每一個應用節(jié)點都要定期保持數據最新救巷,節(jié)點越多壶熏,要維持這樣的情況對數據庫的開銷也越大。
六浦译、存儲方式:
1棒假、堆內存儲:速度快,但是容量有限精盅。
2帽哑、堆外(OffHeapStore)存儲:被稱為BigMemory,只在企業(yè)版本的Ehcache中提供叹俏,原理是利用nio的DirectByteBuffers實現(xiàn)祝拯,比存儲到磁盤上快,而且完全不受GC的影響她肯,可以保證響應時間的穩(wěn)定性;但是direct buffer的在分配上的開銷要比heap buffer大鹰贵,而且要求必須以字節(jié)數組方式存儲晴氨,因此對象必須在存儲過程中進行序列化,讀取則進行反序列化操作碉输,它的速度大約比堆內存儲慢一個數量級籽前。
(注:direct buffer不受GC影響,但是direct buffer歸屬的的JAVA對象是在堆上且能夠被GC回收的敷钾,一旦它被回收枝哄,JVM將釋放direct buffer的堆外空間。)
3阻荒、磁盤存儲挠锥。
七、緩存使用模式:
cache-aside:直接操作侨赡。先詢問cache某條緩存數據是否存在蓖租,存在的話直接從cache中返回數據粱侣,繞過SOR;如果不存在蓖宦,從SOR中取得數據齐婴,然后再放入cache中。
public V readSomeData(K key)
{
Element element;
if ((element = cache.get(key)) != null) {
return element.getValue();
}
if (value = readDataFromDataStore(key)) != null) {
cache.put(new Element(key, value));
}
return value;
}
cache-as-sor:結合了read-through稠茂、write-through或write-behind操作柠偶,通過給SOR增加了一層代理,對外部應用訪問來說睬关,它不用區(qū)別數據是從緩存中還是從SOR中取得的诱担。
read-through。
write-through共螺。
write-behind(write-back):既將寫的過程變?yōu)楫惒降母秒龋诌M一步延遲寫入數據的過程。
Copy Cache的兩個模式:CopyOnRead和CopyOnWrite藐不。
CopyOnRead指的是在讀緩存數據的請求到達時匀哄,如果發(fā)現(xiàn)數據已經過期,需要重新從源處獲取雏蛮,發(fā)起的copy element的操作(pull)涎嚼;
CopyOnWrite則是發(fā)生在真實數據寫入緩存時,發(fā)起的更新其他節(jié)點的copy element的操作(push)挑秉。
前者適合在不允許多個線程訪問同一個element的時候使用法梯,后者則允許你自由控制緩存更新通知的時機。
更多push和pull的變化和不同犀概,也可參見這里立哑。
八、多種配置方式:
包括配置文件姻灶、聲明式配置铛绰、編程式配置,甚至通過指定構造器的參數來完成配置产喉,配置設計的原則包括:
所有配置要放到一起
緩存的配置可以很容易在開發(fā)階段捂掰、運行時修改
錯誤的配置能夠在程序啟動時發(fā)現(xiàn),在運行時修改出錯則需要拋出運行時異常
提供默認配置曾沈,幾乎所有的配置都是可選的这嚣,都有默認值
九、自動資源控制(Automatic Resource Control塞俱,ARC):
它是提供了一種智能途徑來控制緩存姐帚,調優(yōu)性能。特性包括:
內存內緩存對象大小的控制障涯,避免OOM出現(xiàn)
池化(cache manager級別)的緩存大小獲取卧土,避免單獨計算緩存大小的消耗
靈活的獨立基于層的大小計算能力惫皱,下圖中可以看到,不同層的大小都是可以單獨控制的
可以統(tǒng)計字節(jié)大小尤莺、緩存條目數和百分比
優(yōu)化高命中數據的獲取旅敷,以提升性能,參見下面對緩存數據在不同層之間的流轉的介紹