cache是老生常談的事情,這里我想強(qiáng)調(diào)一下KISS原則,就是keep it simple and stupid。最近看到很多場景下cache使用的不適當(dāng),特別是被過度使用了。一個(gè)簡單鍵值存儲并不需要復(fù)雜的cache方案,好的方案就是用最簡單的方法解決問題。簡潔是美!
一個(gè)標(biāo)準(zhǔn)Cache的主要特征是:
過期時(shí)間
容量規(guī)劃(重要)
清除策略(重要)
命中率統(tǒng)計(jì)
基于以上特征,使用HashMap作為本地cache似乎很不適當(dāng)。更重要的是,有不少朋友認(rèn)為HashMap可能會使內(nèi)存耗盡。其實(shí)不然,自jdk1.2后,Java就引入了WeakHashMap??纯碼pi文檔是怎么說的:對于一個(gè)給定的鍵,其映射的存在并不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成為可終止的,被終止,然后被回收。丟棄某個(gè)鍵時(shí),其條目從映射中有效地移除,因此,該類的行為與其他的 Map 實(shí)現(xiàn)有所不同。
這個(gè)翻譯明顯有點(diǎn)爛,總結(jié)一下,和強(qiáng)引用的HashMap相比,WeakHashMap在本身引用不被回收的前提下允許GC回收它的鍵值對。當(dāng)GC發(fā)現(xiàn)內(nèi)存即將耗盡且沒有其他對象可以釋放時(shí),會主動(dòng)回收WeakHashMap所占用的對象。因此使用WeakHashMap作為本地cache是不會造成內(nèi)存耗盡。如果你僅僅需要一個(gè)簡單的鍵值對存儲,而并不關(guān)心命中率統(tǒng)計(jì),那么放心的使用WeakHashMap作為cache吧。
通過簡單的包裝,就可以為你的HashMap增加過期時(shí)間和容量規(guī)劃。而且比其他cache更高效。openfire的DefaultCache就是一個(gè)很好的例子:http://svn.igniterealtime.org/svn/repos/openfire/trunk/src/java/org/jivesoftware/util/cache/DefaultCache.java
DefaultCache的核心是HashMap,另外增加了一層很薄的包裝來實(shí)現(xiàn)過期和LRU。DefaultCache包括兩個(gè)LinkedList,一個(gè)用于存儲插入順序,另一個(gè)用于存儲插入時(shí)間。當(dāng)添加cache時(shí),都addFirst。當(dāng)cache size達(dá)到臨界值時(shí),從最尾部刪除。有朋友測試過,比ehcache快5倍。
如果本地cache不能滿足你的要求,ehcache是個(gè)很好的選擇。不僅僅作為分布式的cache,甚至作為狀態(tài)同步,ehcache都有非常優(yōu)秀的案例。也可以實(shí)現(xiàn)多機(jī)copy。分享一個(gè)數(shù)據(jù),某生產(chǎn)系統(tǒng)中ehcache每天處理17,466,415次replication。
基于ehcache的分布式緩存,你可以簡單的實(shí)現(xiàn)分布式計(jì)算。分析一個(gè)場景,某iOS后端服務(wù)要求用戶先注冊ios設(shè)備,可以按下圖處理設(shè)備信息。DeviceServer接受device注冊后同步copy的本地cache和ehcache。該ehcache按以下方式配置成對。本地cache再定期(每隔幾秒鐘)從ehcache中加載peer server注冊的數(shù)據(jù)。
peer server的hostName和rmiUrls配置需要做相應(yīng)改動(dòng)。
偽代碼如下
根據(jù)KISS原則,建議簡單狀態(tài)同步都使用ehcache完成。知識投入低,且易于維護(hù)。