摘自: http://hi.baidu.com/xproduct/blog/item/28297009f7016ccd3bc76
首先,介紹一下我(作者)自己使用Cache的背景,以便讀者更清楚地了解我下面要講述哪些內容。
我主要是一個Cache實現者,而不是使用者。為了給一些ORM(比如JPA實現)提供Cache支持,我需要包裝其它的Open Source Cache,并考察它們的特性。
我對這些Open Source Cache的一些工作原理,了解得比較多。具體配置和使用細節,了解的比較少。
本文主要講述的也是Cache的特性和工作原理,而不是一個安裝、配置、使用的入門手冊。
本文簡述Cache的一般特性,詳述Cache的高級特性,比如,分布式Cache,關聯對象的Cache,POJO Cache等。
閱讀本文需要具備基本的Cluster知識,ORM知識,數據庫事務知識。本文不解釋這些基本概念。
-------------------------------------------------------
Cache Features
首先,我們來瀏覽一下常見的Cache。
這個鏈接給出了常用的Java Open Source Cache。
http://java-source.net/open-source/cache-solutions
memcached,JBoss Cache,SwarmCache,OSCache,JCS,EHCache等開源項目的出鏡率和關注率比較高。
memcached和其他幾個不同,后面會詳述。
JBoss Cache的特點是,功能大而全,可算是Cache集大成者,幾乎什么都支持。
其余的幾個都很輕量。SwarmCache,OSCache,JCS支持Cluster。EHCache不支持Cluster。
下面列出Cache的基本特性。
1. 時間記錄
數據進入Cache的時間。
2. timeout過期時間
Cache里面的數據多久過期
3. Eviction Policy 清除策略
Cache滿了之后,根據什么策略,應該清除哪些數據。
比如,最不經常被訪問的數據,最久沒有訪問到的數據。
4. 命中率
Cache的數據被選中的比率
5. 分級Cache
有些Cache有分級的概念。比如,幾乎所有的Cache都支持Region分區的概念。可以指定某一類的數據存放在特定的Region里面。JBoss Cache可以支持更多的級別。
6. 分布式Cache
分布在不同計算機上的Cache
7. 鎖,事務,數據同步
一些Cache提供了完善的鎖,事務支持。
以上特性,大部分Cache都有相應的API支持。這些API很直觀,也很簡單,本文不打算展開講述。
本文下面主要介紹,memcached和JBoss Cache這兩個具有代表意義的Cache的高級特性,包括分布式Cache的支持。
-------------------------------------------------------
memcached
http://www.danga.com/memcached/
memcached是一個Client Server結構的遠程Cache實現。
Server是用C寫的,提供了多種語言的客戶端API,包括Java, C#, Ruby, Python, PHP, Perl, C等多種語言。
memcached主要使用在Shared Nothing Architecture中。應用程序通過客戶端API,從memcached server存取數據。
典型的應用,比如,用memcached作為數據庫緩存。
也常有這樣的用法,用memcached存放HTTP Session的數據。具體做法是包裝Session Interface,截獲setAttribute(), getAttribute()方法。
MemcachedSessionWrapper {
Object getAttribute( key ){
return memcachedClient.get (session.getId() + key);
}
void setAttribute( key, value ){
memcachedClient.setObject(session.getId() + key, value);
}
}
不同計算機上的應用程序通過一個IP地址來訪問memcahced Server。
同一個key對應的數據,只存在于一臺memcached server的一份內存中。
memcached server也可以部署在多臺計算機上。Memcached通過key的hashcode來判斷從哪臺memcached server上存取數據數據。我們可以看到,同一個key對應的數據,還是只存在于一臺memcached server的一份內存中。
所以,memcached不存在數據同步的問題。這個特性很關鍵,我們后面講到Cluster Cache的時候,就會涉及到數據同步的問題。
memcached由于是遠程Cache,要求放到Cache的Key和Value都是Serializable。
遠程Cache,最令人擔心的網絡通信開銷。據有經驗的人說,memcached網絡通信開銷很小。
memcached的API設計也是遠程通信友好的,提供了getMulti()等高粒度的調用方法,能夠批量獲取數據,從而減少網絡通信次數。
-------------------------------------------------------
JBoss Cache
http://www.jboss.org/products/jbosscache
有一個商業Cluster Cache,叫做tangosol。
JBoss Cache是我唯一知道的能夠和tangosol媲美的開源Cache。
Cluster Cache的數據同步,需要網絡通信,這就要求放到Cache的數據是Serializable。
JBoss Cache提出了POJO Cache的概念,意思是數據不是Serializable,一樣能夠在Cluster中同步。
JBoss POJO Cache通過AOP機制,支持對象同步,支持對象屬性的同步,支持關聯對象的Cache,支持繼承,集合,Query,并支持不同級別的事務,儼然一個小型內存數據庫級別的數據存儲系統。
下面進行解釋。
最令人迷惑不解的是這個POJO的Cluster同步如何實現。
JBoss POJO Cache采用AOP來照管了POJO的通信和傳播工作。天下沒有免費的午餐,POJO不支持序列化,框架本身就要做這個工作——Marshal and Unmarshal,比如通過把Java對象翻譯成XML,傳播出去,對方收到XML,再翻譯成Java對象。
上面說了,JBoss POJO Cache很像一個小型存儲容器,JBoss POJO Cache的對象管理也非常類似Hibernate,JDO,JPA等ORM工具,同樣有Detach和Attach的概念。
Attach就是put,把對象放入到Cache中。Detach就是remove,把對象從Cache中刪除。為啥要多起個名?
原因是,put的時候,放進去的是個干干凈凈的POJO,出來的時候,就是Enhanced Object,里面夾雜了很多Interceptor代碼,監聽對象的方法。
你操作這個對象的時候,JBoss AOP框架就獲得了相應的通知,能夠做出相應的反應,比如數據同步等。
JBoss POJO Cache支持對集合類型的AOP。同樣需要把集合Attach(Put)進Cache,然后get出來,然后對集合進行操作,就可以被JBoss AOP截獲了。
JBoss POJO Cache的基礎是JBoss Tree Cache。這個Tree Cache類似于一個XML DOM樹形數據結構。
JBoss Cache采用Full Qualified Name作為Cache Key,類似于XPath。比如,a/b/c/d。
當你刪除a/b的時候,a/b/c,a/b/c/d等所有屬于a/b的Key和對應數據,都被刪除。
JBoss Cache的findObjects方法能夠找出一串對象。比如,findObjects根據a/b/c/d能夠找出a,b,c,d等4個對象,放在一個Map中返回。
具體用法要參見API詳細說明,因為JBoss POJO Cache提供了很多行為模式。
這 種分級的Cache功能很有用,實現起來也不難。只是,我覺得,還是不夠強大。既然支持了類似于XPath的Key,不如索性支持XPath的條件查詢。 比如,a[name=”n”]/b/c。當然,實現這種功能的代價非常大,需要遍歷整個Cache Tree,正如XPath需要遍歷整個DOM節點一樣。
最后,JBoss Cache和tangosol一樣,都支持了一個我認為如同雞肋一般的功能,鎖機制和事務支持。這個事務支持的意思是,Cache本身實現了類似于數據庫的4種事務隔離級別。
在我看來,這種支持無疑是為了賺取眼球。Cache不當做Cache來用,搞些歪門邪道,大而不當。想當作數據庫來用,那還不如把主要功夫花在上述提到的那種精確批量查詢功能上。
-------------------------------------------------------
Cluster同步
Cluster 之間的Cache同步有多種實現方法。比如,JMS,RMI,Client Server Socket等方法,用的最多的,支持最廣的方法是JGroups開源項目實現的Multicast。配置Cluster Cache,通常就相當于配置JGroups,需要閱讀JGroups配置文檔。
Cache的操作通常有4個,get,put,remove,clear。
對 于Cluster Cache來說,讀操作(get)肯定是Local方法,只需要從本臺計算機內存中獲取數據。Remove/clear兩個寫操作,肯定是Remote方 法,需要和Cluster其他計算機進行同步。Put這個寫方法,可以是Local,也可以是Remote的。
Remote Put方法的場景是這樣,一臺計算機把數據放到Cache里面,這個數據就會被傳播到Cluster其他計算機上。這個做法的好處是Cluster各臺計算機的Cache數據可以及時得到補充,壞處是傳播的數據量比較大,這個代價比較大
Local Put方法的場景是這樣,一臺計算機把數據放到Cache里面,這個數據不會被傳播到Cluster其他計算機上。這個做法的好處是不需要傳播數據,壞處 是Cluster各臺計算機的Cache數據不能及時得到補充,這個不是很明顯的問題,從Cache中得不到數據,從數據庫獲取數據是很正常的現象。
Local Put比起Remote Put的優勢很明顯,所以,通常的Cluster Cache都采用Local Put的策略。各Cache一般都提供了Local Put的配置選項,如果你沒有看到這個支持,那么請換一個Cache。
-------------------------------------------------------
Center vs Cluster
Memcached可以看作是Center Cache。
Center Cache和Cluster Cache的特性比較如下:
Center Cache沒有同步問題,所以,remove/clear的時候,比較有優勢,不需要把通知發送到好幾個計算機上。
但是,Center Cache的所有操作,get/put/remove/clear都是Remote操作。而Cluster Cache的get/put都是Local操作,所以,Cluster Cache在get/put操作上具有優勢。
Local get/put在關聯對象的組裝和分拆方面,優勢比較明顯。
關聯對象的分拆是這個意思。
比如,有一個Topic對象,下面有幾個Post對象,每個Post對象都有一個User對象。
Topic對象存放到Cache中的時候,下面的關聯對象都要拆開來,分成各自的Entity Region來存放。
Topic Region -> Topic ID -> Topic Object
Post Region -> Post ID -> Post Object
User Region -> User ID -> User Object
這個時候,put的動作可能發生多次。Remote Put的開銷就比較大。
Get的過程類似,也需要get多次,才能拼裝成一個完整的Topic對象。
-------------------------------------------------------
過期數據
Cache可以用在任何地方,比如,頁面緩存。但Cache的最常用場景是用在ORM中,比如,Hibernate,JDO,JPA中。
ORM Cache的使用方法有個原則——不要把沒有Commit的修改數據放入到緩存中。這是為了防止Read Dirty。
數據庫事務分為兩種,一種是讀事務,不修改數據,一種是寫事務,修改數據。
寫事務的操作流程如下:
db.commt();
cache.remove(key); // 這一步操作,清除了Cache數據,也記錄了一個時間removeTime。
讀事務的操作流程如下:
readTime = current time;
data = cache.get(key);
if(data is null){
data = db.load(key);
cache.put(key, data, readTime); // 這里要readTime傳進去
}
這里需要注意的是put的時候,需要readTime這個參數。
這個readTime要和上一次的removeTime進行比較。
如果readTime > removeTime,這個put才能成功,數據才能夠進入緩存。
這是為了保證不把過期數據放入到Cache中,及時反映數據庫的變化。
另外,需要注意的是,cache.remove(key); 這個事件需要傳播到Cluster其他計算機,通知它們清理緩存。
為什么需要這個通知?
一定要注意,這不是為了避免并發修改沖突。并發修改沖突的避免需要引入樂觀鎖版本控制機制。
有可能存在這樣的誤解,認為有了樂觀鎖版本控制機制,就不需要Cache.remove通知了。這是不對的。
Cache.remove通知的主要目的是,保證緩存能夠及時清理過期數據,反映數據的變化,保證大部分時間內,應用程序顯示給用戶的不是過期數據。
另外,db.commt(); cache.remove(key); 這兩步調用之間,有很小的可能發生另外的事務。這段極小的時間內,可能無法保證Read Committed,可能出現很短期的過期數據。
為什么說很短期,因為緊接著的Cache.remove就會清理過期數據。
如果偏執到這種程度,這么短期的幾乎不可能發生的小概率事件,都不能容忍,那么可以,db.commt()之前,給Cache加一個悲觀鎖,不讓別的事務,把數據Put進入Cache,就可以防止這個小概率、微影響的事件。
JBoss Cache和Tangosol就提供了這類雞肋一般的悲觀鎖機制。典型的開發資源配置不當,有用的需要的不做,沒用的功能使勁做。
ORM Query Cache
ORM Cache一般分為兩種。一種是ID Cache(ORM文檔中稱為二級Cache),用來存放Entity ID對應的Entity對象;一種是Query Cache,用來存放一條查詢語句對應的查詢結果集。
ID Cache非常直觀,如同上述講述的,一般是一個Entity Class對應一個Region,Entity存放到對應的Region里面。
Query Cache比較復雜,而且潛在作用很大,值得仔細講解。
現有的ORM對Query Cache的支持并不是很理想。
比如,Hibernate把整個結果集直接放在Query Cache中。這樣,有任何風吹草動,發生了任何數據庫的寫操作,Query Cache都需要清空。
有 一種比較好的做法,把ID List存放在Query Cache中,每次獲取的時候,先獲取ID List,然后根據ID List獲取Entity List。Query Cache根據Query涉及到的Table Name來進行清理,一旦發生對這些Table Name的修改操作,就可以根據不同情況,清理Query Cache。
比如,select t2.* from t1, t2 where t1.id = t2.foreign_id and t1.name = ‘a’
那么insert into t1, delete from t1, insert into t2, delete from t2都會清除這條Query Cache。
同樣的 update t1 set 這樣的語句也會清除這條Query Cache。
Hibernate為什么不這么做,因為Query Cache的情況比較復雜。也許選擇的結果集并不是只有一個Entity類型,也許只是幾個字段。
這個地方,如果細分,還是有很多功夫可以做的。而且也很值得花功夫做,因為Query Cache對于性能的提高,有很大作用。
-------------------------------------------------------
ORM Query Cache
Cache可以用在任何地方,比如,頁面緩存。但Cache的最常用場景是用在ORM中,比如,Hibernate,JDO,JPA中。
ORM Cache一般分為兩種。一種是ID Cache(ORM文檔中稱為二級Cache),用來存放Entity ID對應的Entity對象;一種是Query Cache,用來存放一條查詢語句對應的查詢結果集。
ID Cache非常直觀,如同上述講述的,一般是一個Entity Class對應一個Region,Entity存放到對應的Region里面。
Query Cache比較復雜,而且潛在作用很大,值得仔細講解。
現有的ORM對Query Cache的支持并不是很理想。
比如,Hibernate把整個結果集直接放在Query Cache中。這樣,有任何風吹草動,發生了任何數據庫的寫操作,Query Cache都需要清空。
有 一種比較好的做法,把ID List存放在Query Cache中,每次獲取的時候,先獲取ID List,然后根據ID List獲取Entity List。Query Cache根據Query涉及到的Table Name來進行清理,一旦發生對這些Table Name的修改操作,就可以根據不同情況,清理Query Cache。
比如,select t2.* from t1, t2 where t1.id = t2.foreign_id and t1.name = ‘a’
那么insert into t1, delete from t1, insert into t2, delete from t2都會清除這條Query Cache。
同樣的 update t1 set 這樣的語句也會清除這條Query Cache。
Hibernate為什么不這么做,因為Query Cache的情況比較復雜。也許選擇的結果集并不是只有一個Entity類型,也許只是幾個字段。
這個地方,如果細分,還是有很多功夫可以做的。而且也很值得花功夫做,因為Query Cache對于性能的提高,有很大作用。
-----------------------------------------------------------
Query Key
Query Cache的性能需要考慮幾個方面。比如,Query Key。Query Key一般由2個部分組成:Query String部分,SQL, HQL, EQL, or OQL;參數部分。
尋找Query Key的對應數據的時候,Query Key的比較有兩個步驟,先hash,然后equals。所以,Query Key的hashcode和equals兩個方法很重要。尤其是equals方法。
equals 方法需要比較很長的Query String。如果沒有命中,Query String不相等,那么開銷很小,因為通常來說,不相等的String長度都不同,或者前面的字符串都不相同。開銷最大的是命中的時候,Query String相等,那么需要把String從頭比到尾。
我們可以采取一些方法來提高String的比較速度。比如,大部分的情況屬于靜態查 詢,我們可以采用Singleton String。相同reference的String之間的比較速度很快。對于ORM來說,最好直接使用最外面的HQL, EQL, OQL作為Query Key,而不是采用生成的SQL結果。因為生成的SQL結果每次都是一個新String,具有不同的reference,Cache命中的時候,需要比較 整個字符串。
動態拼裝的Query String的性能提高比較難辦。因為最終的結果,都是一個新String。我采用的一種方式是,動態拼裝的結果是一個String[]。兩個 String[]如果相等,那么里面的元素String的reference都是相等的,這是由JVM對一個Class內部的String常量進行優化的 結果。
比如,
String[] a = {
“select * from t where”
“a = 1”
“and b = 2”
};
String[] b = {
“select * from t where”
“a = 1”
“and b = 2”
};
那么a和b的比較只需要3次String reference的比較
?
這個是不正確得,ehcache1.2之后就支持cluster了,
還有一點就是關于query cache描述也不是很正確,
1 query cache其實應該是一個很大得范疇,不應該只用在orm那一層,也不局限把sql語句作為key,orm里這樣使用定義query cache只是情況之一。
2 查詢結果集也可以被緩存。只有當經常使用同樣的參數進行查詢時,這才會有些用處。在查詢緩存中,它并不緩存結果集中所包含的實體的確切狀態;它只緩存這些 實體的標識符屬性的值、以及各值類型的結果。需要將打印sql語句與最近的cache內容相比較,將不同之處修改到cache中,所以查詢緩存通常會和二 級緩存一起使用。而query cache雖然能提高性能,但是按照hibernate in action的話來說適用場景較少。
而如果我們放大query cache的概念,那么對于實時性要求不是很高的數據,它就能極大的提高性能,比如說主頁,如果主頁更新的時間為5分鐘,那么query cache就能發揮其所長了。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
