此文隻對磁盤緩存方式進行展述
hibernate執行sql,将資料轉化為映射實體類後會進行資料緩存,其中調用AbstractRowReader#performTwoPhaseLoad方法時,會循環資料集,調用TwoPhaseLoad.initializeEntity();該方法會調用ehcache的put方法進行資料緩存。
ehcache在put資料執行個體(資料執行個體已轉化為Element執行個體對象),該對象到磁盤時(此時記憶體緩存已滿,按照ehcache.xml中設定的政策,超過部分存儲在磁盤),會判斷目前disk store spool is full,如果已滿,需進行50ms的sleep,降低生産線程放入的速度。
Cache.class
/**
* Put an element in the cache.
* <p/>
* Resets the access statistics on the element, which would be the case if it has previously been
* gotten from a cache, and is now being put back.
* <p/>
* Also notifies the CacheEventListener that:
* <ul>
* <li>the element was put, but only if the Element was actually put.
* <li>if the element exists in the cache, that an update has occurred, even if the element would be expired
* if it was requested
* </ul>
* Caches which use synchronous replication can throw RemoteCacheException here if the replication to the cluster fails.
* This exception should be caught in those circumstances.
*
* @param element A cache Element. If Serializable it can fully participate in replication and the DiskStore. If it is
* <code>null</code> or the key is <code>null</code>, it is ignored as a NOOP.
* @param doNotNotifyCacheReplicators whether the put is coming from a doNotNotifyCacheReplicators cache peer, in which case this put should not initiate a
* further notification to doNotNotifyCacheReplicators cache peers
* @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
* @throws IllegalArgumentException if the element is null
*/
private void putInternal(Element element, boolean doNotNotifyCacheReplicators, boolean useCacheWriter) {
putObserver.begin();
......
backOffIfDiskSpoolFull(); //判斷buffer是否已滿
element.updateUpdateStatistics();
boolean elementExists = false;
if (useCacheWriter) {// useCacheWriter=false
......
} else {
//最終調用DiskStore.put方法将資料放入磁盤
elementExists = !compoundStore.put(element);
notifyPutInternalListeners(element, doNotNotifyCacheReplicators, elementExists);
}
putObserver.end(elementExists ? PutOutcome.UPDATED : PutOutcome.ADDED);
}
/**
* wait outside of synchronized block so as not to block readers
* If the disk store spool is full wait a short time to give it a chance to
* catch up.
* todo maybe provide a warning if this is continually happening or monitor via JMX
*/
private void backOffIfDiskSpoolFull() {
// 若基于記憶體,調用的是MemoryStore的bufferFull方法,此方法直接傳回false
// 若基于記憶體+磁盤,調用的是DiskStorageFactory的bufferFull方法
if (compoundStore.bufferFull()) {
// back off to avoid OutOfMemoryError
try {
Thread.sleep(BACK_OFF_TIME_MILLIS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 計算隊列大小 diskSpoolBufferSizeMB * 1024 * 1024
this.queueCapacity = cache.getCacheConfiguration().getDiskSpoolBufferSizeMB() * MEGABYTE;
/**
* 判斷緩存是否已滿
* Return {@code true} if the disk write queue is full.
*
* @return {@code true} if the disk write queue is full.
*/
public boolean bufferFull() {
//diskQueue表示DiskWriter線程池(ScheduledThreadPoolExecutor)中的隊列DelayedWorkQueue
//elementSize表示Element執行個體序列化後的位元組大小
return (diskQueue.size() * elementSize) > queueCapacity;
}
等sleep結束後,調用DiskStore.put方法将Element轉化為PlaceHolder,添加到DiskStore中,并在添加完成後調用PlaceHolder的installed()方法,installed()方法會使用DiskStorageFactory schedule一個PersistentDiskWriteTask,将該PlaceHolder寫入到磁盤(在DiskStorageFactory有一個DiskWriter線程池會在一定的時候執行該Task)生成一個DiskMarker,釋放PlaceHolder占用的記憶體。在從DiskStore移除一個Element時,它會先讀取磁盤中的資料,将其解析成Element,然後釋放這個Element占用的磁盤空間,并傳回這個被移除的Element。在從DiskStore讀取一個Element時,它需要找到DiskStore中的DiskSubstitute,對DiskMarker讀取磁盤中的資料,解析成Element,然後傳回。
ehcache.xml配置參數含義:
maxElementsInMemory:記憶體中允許存儲的最大的元素個數,0代表無限個
clearOnFlush:記憶體數量最大時是否清除。
eternal :設定緩存中對象是否為永久的,如果是,逾時設定将被忽略,對象從不過期。根據存儲資料的不同,例如一些靜态不變的資料如省市區等可以設定為永不過時
timeToIdleSeconds : 設定對象在失效前的允許閑置時間(機關:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,預設值是0,也就是可閑置時間無窮大。
timeToLiveSeconds :緩存資料的生存時間(TTL),也就是一個元素從建構到消亡的最大時間間隔值,這隻能在元素不是永久駐留時有效,如果該值是0就意味着元素可以停頓無窮長的時間。
overflowToDisk :記憶體不足時,是否啟用磁盤緩存。
maxEntriesLocalDisk:當記憶體中對象數量達到maxElementsInMemory時,Ehcache将會對象寫到磁盤中。
maxElementsOnDisk:硬碟最大緩存個數。
diskPersistent:是否在VM重新開機時存儲硬碟的緩存資料。預設值是false。
diskExpiryThreadIntervalSeconds:磁盤失效線程運作時間間隔,預設是120秒。
diskSpoolBufferSizeMB:這個參數設定DiskStore(磁盤緩存)的緩存區大小。預設是30MB。每個Cache都應該有自己的一個緩沖區。
官方解釋:This is the size to allocate the DiskStore for a spool buffer. Writes are made
to this area and then asynchronously written to disk. The default size is 30MB.
Each spool buffer is used only by its cache. If you get OutOfMemory errors consider
lowering this value. To improve DiskStore performance consider increasing it. Trace level
logging in the DiskStore will show if put back ups are occurring.
若遇到加載資料過慢(一般情況下是因為在同一時間 調用hibernate擷取資料過大,然後hibernate調用ehcache二級緩存進行資料緩存時,ehcache判斷隊列中的元素已超過buffer限制,産生了sleep,造成加載資料過慢的現象),需擴大diskSpoolBufferSizeMB值
參考:Java Cache-EHCache系列之Store實作