當前最常用的三個緩存組件:ehcache、redis、memcached
其中,ehcache與應用共同運行於JVM中,屬於嵌入式組件,運行效率最高,因此常被用於實現一級緩存。
在更復雜的一些系統中,由於ehcache對集群/分布式的支持相對較弱,因此還會集成redis、memcached等,實現二級緩存。
ehcache的用法非常簡單,只需要引入相關的Jar包,並創建一個配置文件,就可以在開發中使用了。
1、在Maven中添加ehcache的依賴:
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.4</version> </dependency>
這個版本是發布到net.sf.ehcache的最后一個版本,也是2.x的最后一個版本。
在org.ehcache上有更新的3.x版本,功能更強大,寫法差異也挺大。
由於2.x的核心功能已經非常穩定,已經完全滿足系統需求,也更熟悉,因此我還是選擇了這個2.10.4的版本。
2、創建配置文件:ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd" updateCheck="false" dynamicConfig="false"> <diskStore path="java.io.tmpdir/myApp"/> <!-- 默認緩存 屬性說明: maxElementsInMemory:內存中可保存的最大數量 eternal:緩存中對象是否為永久的。如果是,超時設置將被忽略 timeToIdleSeconds:對象最后一次訪問之后的存活時間 timeToLiveSeconds:對象創建后的存活時間 memoryStoreEvictionPolicy:內存緩存的超期清理策略 maxElementsOnDisk:硬盤中可保存的最大數量 diskExpiryThreadIntervalSeconds:磁盤超期監控線程掃描時間間隔 overflowToDisk:內存不足時,是否啟用磁盤緩存 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="1200" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" overflowToDisk="true"> </defaultCache> </ehcache>
把這個配置文件保存到 src/main/resource 目錄下即可。
3、實現動態創建Cache
在ehcache中,有兩個最基本的對象:Cache 和 Element
其中,Cache相當於ehcache的分區,可以看成memcache中的Slab,上面配置文件中的 defaultCache 就是一個默認分區
每個Cache(分區)都類似一個Map<K, V>,可以通過Key來從Cache中返回緩存的Value。
每個KV鍵值對,在ehcache中,被稱為Element(元素)。
要將任何一個對象添加到ehcache中,都需要事先指定分區
但在配置文件中創建分區很麻煩,通常只創建一個默認分區(必須存在),然后通過一個方法來動態創建分區:
/** * 獲取Cache,當Cache不存在時自動創建 * * @param cacheName * @return Cache
* @author netwild@qq.com */ public Cache getOrAddCache(String cacheName) { Cache cache = cacheManager.getCache(cacheName); if (cache == null) { synchronized (locker) { cache = cacheManager.getCache(cacheName); if (cache == null) { cacheManager.addCacheIfAbsent(cacheName); cache = cacheManager.getCache(cacheName); } } } return cache; }
這樣的話,只需要像下面的用法,就可以很方便的把對象添加到緩存中:
String cacheName = "article";
String atricleId = "A00428";
Atricle article = AtricleService.findById(atricleId);
Element element = new Element(atricleId, article); getOrAddCache(cacheName).put(element);
動態創建的Cache並不會出現在ehcache.xml配置文件中。
值得注意的是,上面動態創建Cache的方法中,並沒有為新的Cache指定任何參數,那這些參數的默認值是多少呢?
其實,當創建Cache時,如果未傳入參數默認值,將自動拷貝 defaultCache 的參數設置
就是說,配置文件中 defaultCache 的超期時間等屬性將直接被應用到所有動態創建的Cache。
4、ehcache關於元素超期的判斷邏輯
在ehcache.xml配置文件中,有兩個關於元素超期的參數:
timeToLiveSeconds:對象創建后的存活時間
timeToIdleSeconds:對象最后一次訪問之后的存活時間
這兩個參數我忽略了將近一年的時間,一直在配置文件中對他們都設置了同樣的參數值,比如:1200(20分鍾)
剛才反復實驗多次,終於將這兩個參數搞清楚,才明白以前的做法是錯誤的
首先,這兩個參數都可以單獨設置而省略另一個,也可以分別設置成不同的值,當然也可以設置成相同的值,就像我以前做的那樣
下面分別對這幾種進行說明
1)單獨設置 timeToLiveSeconds:
該對象的超期時間 = 初始創建時間 + timeToLiveSeconds
因為初始創建時間是固定的,因此不管這個對象在有效期內被命中了多少次,一旦滿足超期條件,該對象將被移除。
2)單獨設置 timeToIdleSeconds:
該對象的超時時間 = 最近訪問時間 + timeToIdleSeconds
注意與上面的區別:不再根據創建時間,而是根據最近訪問時間來確定超期時間
所以這是一種動態的超期模式,即使這個參數設置為1(秒),只要保證每秒內都能get一次,那么對象也將永遠不會超期。
3)分別設置 timeToLiveSeconds 及 timeToIdleSeconds :
那么將分別計算以上兩種模式的超期時間,會得出兩個結果,再從兩個結果里找到最小的一個做為超期時間,相當於“嚴苛模式”
但事實上,創建時間肯定會小於最近訪問時間,那如果兩者都設置同樣的參數值,相當於 timeToIdleSeconds 永遠也不會起到作用。
如果設置不同的參數值,根據具體的業務需求,可能會出現一些意料之中或者意料之外的情況。
綜上所述,我的建議是,單獨設置 timeToIdleSeconds 更恰當一些,對於在有效期內被頻繁命中的緩存對象,可以自動“續期”。
5、最常用的操作之一:判斷緩存中是否存在對象
在應用緩存的開發過程中,這是最常用的操作,目的是想要知道:目標對象是否已經被緩存過
通常下面的邏輯是:如果已被緩存過,那么直接拿出來使用;否則自力更生,完事之后再添加到緩存,下次就省事了
很多人是這樣判斷的:
return getOrAddCache(cacheName).get(key) != null;
但這種方式存在個問題:當緩存對象實際上存在,但值就是Null,這時就相當於忽略了緩存
所以我開始時是這樣判斷的:
return getOrAddCache(cacheName).isKeyInCache(key);
后來發現這種方式不會進行超期驗證,就是說即使對象已經超期,只要當初被創建過,也會返回true
調整之后:
Cache cache = getOrAddCache(cacheName); if(cache.isKeyInCache(key) && cache.getQuiet(key) != null){ return true; } return false;
這樣就准確了!