mybatis緩存機制詳解(二)——緩存裝飾器

LruCache最近最少使用的回收策略:
java

package org.apache.ibatis.cache.decorators;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;

/**
 * 基於最近最少使用算法的回收策略
 * @author easy288
 *
 */
public class LruCache implements Cache {

	// 代理的緩存對象
	private final Cache delegate;
	// 此Map的key和value都是要添加的鍵值對的鍵
	private Map<Object, Object> keyMap;
	// 最老的key
	private Object eldestKey;

	public LruCache(Cache delegate) {
		this.delegate = delegate;
		setSize(1024);
	}

	@Override
	public String getId() {
		return delegate.getId();
	}

	@Override
	public int getSize() {
		return delegate.getSize();
	}

	public void setSize(final int size) {
		keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
			private static final long serialVersionUID = 4267176411845948333L;

			protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
				boolean tooBig = size() > size;
				if (tooBig) {
					eldestKey = eldest.getKey();
				}
				return tooBig;
			}
		};
	}

	@Override
	public void putObject(Object key, Object value) {
		delegate.putObject(key, value);
		cycleKeyList(key);
	}

	@Override
	public Object getObject(Object key) {
		keyMap.get(key); // touch
		return delegate.getObject(key);
	}

	@Override
	public Object removeObject(Object key) {
		return delegate.removeObject(key);
	}

	@Override
	public void clear() {
		delegate.clear();
		keyMap.clear();
	}

	public ReadWriteLock getReadWriteLock() {
		return null;
	}

	/**
	 * 循環keyList
	 * @param key
	 */
	private void cycleKeyList(Object key) {
		keyMap.put(key, key);
		if (eldestKey != null) {
			delegate.removeObject(eldestKey);
			eldestKey = null;
		}
	}

}

LruCache內部維護一個Map,算法

private Map<Object, Object> keyMap;

它的實際類型是LinkedHashMap<Object,Object>的匿名子類,子類重寫了removeEldesEntry方法,用於獲取在達到容量限制時被刪除的key。數據庫

public void setSize(final int size) {
		keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
			private static final long serialVersionUID = 4267176411845948333L;

			protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
				boolean tooBig = size() > size;
				if (tooBig) {
					eldestKey = eldest.getKey();
				}
				return tooBig;
			}
		};
	}

在每次調用setSize方法時,都會建立一個新的該類型的對象,同時指定其容量大小。第三個參數爲true表明Map中的鍵值對列表要按照訪問順序排序,每次被方位的鍵值對都會被移動到列表尾部(值爲false時按照插入順序排序)。apache

LruCache只是修改了緩存的添加方式,數組

public void putObject(Object key, Object value) {
		delegate.putObject(key, value);
		cycleKeyList(key);
	}

在每次給代理緩存對象添加完鍵值對後,都會調用cycleKeyList方法進行一次檢查。緩存

/**
	 * 循環keyList
	 * @param key
	 */
	private void cycleKeyList(Object key) {
		// 把剛剛給代理緩存對象中添加的key,同時添加到keyMap中
		keyMap.put(key, key);
		// 若是eldestKey不爲null,則表明keyMap內部刪除了eldestKey這個key
		if (eldestKey != null) {
			// 一樣把代理緩存對象中key爲eldestKey的鍵值對刪除便可
			delegate.removeObject(eldestKey);
			eldestKey = null;
		}
	}

LruCache把新添加的鍵值對的鍵添加到keyMap中,若是發現keyMap內部刪除了一個key,則一樣把代理緩存對象中相同的key刪除。數據結構

LruCache就是以這種方式實現最近最少訪問回收算法的。mybatis

ScheduledCache調度緩存裝飾器:多線程

它的內部維護2個字段,clearInterval和lastClear併發

        // 調用clear()清空緩存的時間間隔,單位毫秒,默認1小時
	protected long clearInterval;
	// 最後一次清空緩存的時間,單位毫秒
	protected long lastClear;

在對代理的緩存對象進行任何操做以前,都會首先調用clearWhenStale()方法檢查當前時間點是否須要清理一次緩存,若是須要則進行清理並返回true,不然返回false。

        /**
	 * 當緩存過時時調用clear方法進行清空
	 * @return 若是成功進行了清理則返回true,不然返回false
	 */
	private boolean clearWhenStale() {
		// 若是當前時間-最後一次清空時間>指定的時間間隔,則調用clear()進行清空
		if (System.currentTimeMillis() - lastClear > clearInterval) {
			clear();
			return true;
		}
		return false;
	}

SerializedCache序列化緩存裝飾器:

它負責在調用putObject(key,value)方法保存key/value時,把value序列化爲字節數組;在調用getObject(key)獲取value時,把獲取到了字節數組再反序列化回來。

使用此裝飾器的前提是:全部要緩存的value必須實現Serializable接口,不然會拋出CacheException異常。

        @Override
	public void putObject(Object key, Object object) {
		if (object == null || object instanceof Serializable) {
			delegate.putObject(key, serialize((Serializable) object));
		} else {
			throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
		}
	}

	@Override
	public Object getObject(Object key) {
		Object object = delegate.getObject(key);
		return object == null ? null : deserialize((byte[]) object);
	}

在反序列化時,SerializedCache使用了內部定義的類CustomObjectInputStream,此類繼承自ObjectInputStream,重寫了父類的resolveClass方法,區別在於解析加載Class對象時使用的ClassLoader類加載器不一樣。

public static class CustomObjectInputStream extends ObjectInputStream {

		public CustomObjectInputStream(InputStream in) throws IOException {
			super(in);
		}

		@Override
		protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
			return Resources.classForName(desc.getName());
		}

	}

LoggingCache日誌緩存裝飾器:

它內部維護2個字段:requests查詢次數計數器和hits查詢命中次數計數器

        // 日誌記錄器
	private Log log;
	// 代理的緩存對象
	private Cache delegate;
	// 每次調用getObject(key)查詢時,此值+1
	protected int requests = 0;
	// 每次調用getObject(key)獲取到的value不爲null時,此值+1
	protected int hits = 0;

在每次調用getObject方法從緩存對象中查詢值時,都會迭代這兩個計數器,而且計算實時命中率,打印到日誌中。

        @Override
	public Object getObject(Object key) {
		requests++;
		final Object value = delegate.getObject(key);
		if (value != null) {
			hits++;
		}
		// 打印當前緩存對象的命中率
		if (log.isDebugEnabled()) {
			log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
		}
		return value;
	}
          /**
	 * 計算實時命中率
	 * @return
	 */      
        private double getHitRatio() {
		return (double) hits / (double) requests;
	}

SynchronizedCache同步的緩存裝飾器:

這個裝飾器比較簡單,只是把全部操做緩存對象的方法上都加了synchronized,用來避免多線程併發訪問。

        @Override
	public synchronized int getSize() {
		return delegate.getSize();
	}

	@Override
	public synchronized void putObject(Object key, Object object) {
		delegate.putObject(key, object);
	}

	@Override
	public synchronized Object getObject(Object key) {
		return delegate.getObject(key);
	}

	@Override
	public synchronized Object removeObject(Object key) {
		return delegate.removeObject(key);
	}

	@Override
	public synchronized void clear() {
		delegate.clear();
	}



FifoCache先進先出緩存回收策略裝飾器:

它內部維護一個不限容量的LinkedList,名稱爲keyList,在構造方法中被建立。

private LinkedList<Object> keyList;
  public FifoCache(Cache delegate) {
    this.delegate = delegate;
    this.keyList = new LinkedList<Object>();
    this.size = 1024;
  }

在調用putObject添加緩存時,會在向代理的緩存對象中添加數據以前,調用cycleKeyList進行一次驗證,若是keyList超過限制長度,則進行回收。

        @Override
	public void putObject(Object key, Object value) {
		cycleKeyList(key);
		delegate.putObject(key, value);
	}
        private void cycleKeyList(Object key) {
		// 把key添加到keyList中
		keyList.addLast(key);
		// 若是keyList超長,則移除第一個key,並獲取被移除的key
		// 以後從代理緩存對象中,刪除這個key
		if (keyList.size() > size) {
			Object oldestKey = keyList.removeFirst();
			delegate.removeObject(oldestKey);
		}
	}


SoftCache軟引用緩存裝飾器:

它利用JDK的SoftReference軟引用,藉助垃圾回收器進行緩存對象的回收。

咱們一般使用的引用方式都是強引用,如:Object obj = new Object();只要引用變量obj不爲null,那麼new出來的Object對象永遠不會被垃圾回收器回收。而軟引用的引用方式是這樣的:

SoftReference ref = new SoftReference(new Object());

引用變量ref引用SoftReference對象(這屬於強引用),再由SoftReference 對象內部引用new出來的Object對象(這屬於軟引用)。

自JDK1.2之後引入了java.lang.ref包,其中包含SoftReference(軟引用)、WeakReference(弱引用)和PhantomReference(虛引用)三個引用類,引用類的主要功能就是實現被引用的對象仍能夠被垃圾回收器回收這樣一個功能。具體關於引用類的詳解請點擊http://blog.csdn.net/kavendb/article/details/5935577


SoftCache數據結構以下:

// 強引用集合,最近一此查詢命中的對象,其引用會被加入此集合的頭部
	// 集合採起先進先出策略,當長度超出指定size時,刪除尾部元素
	private final LinkedList<Object> hardLinksToAvoidGarbageCollection;
	// 此隊列保存被垃圾回收器回收的對象所在的Reference對象
	// 垃圾回收器在進行內存回收時,會把Reference對象內的引用變量置爲null,同時將Reference對象加入隊列中
	private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
	// 代理的緩存對象
	private final Cache delegate;
	// 強引用集合長度限制,可經過setSize方法設置,默認爲256
	private int numberOfHardLinks;

SoftCache在寫緩存以前,會先調用removeGarbageCollectedItems()方法刪除已經被垃圾回收器回收的key/value,以後想緩存對象中寫入SoftEntry類型的對象(定義在SoftCache的內部,是SoftReference類的子類)。

@Override
	public void putObject(Object key, Object value) {
		removeGarbageCollectedItems();
		delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
	}
/**
	 * 從緩存對象中刪除已經被垃圾回收器回收的value對象對應的key
	 */
	private void removeGarbageCollectedItems() {
		SoftEntry sv;
		// 彈出隊列中的全部key,依次刪除
		while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
			delegate.removeObject(sv.key);
		}
	}

SoftCache在讀緩存時,是直接讀取的,這樣存在一個問題:緩存的value對象已經被垃圾回收器回收,可是該對象的軟引用對象還存在,這種狀況下要刪除緩存對象中,軟引用對象對應的key。另外,每次調用getObject查詢到緩存對象中的value還未被回收時,都會把此對象的引用臨時加入強引用集合,這樣確保該對象不會被回收。這種機制保證了訪問頻次越搞的value對象,被回收的概率越小。

@Override
	public Object getObject(Object key) {
		Object result = null;
		@SuppressWarnings("unchecked") // assumed delegate cache is totally
										// managed by this cache
		SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
		// 引用變量爲null,說明不存在key指定的鍵值對
		if (softReference != null) {
			// 鍵值對存在的狀況下,獲取軟引用變量引用的對象
			result = softReference.get();
			// 若是引用的value已經被回收,則刪除緩存對象中的key
			if (result == null) {
				delegate.removeObject(key);
			} else {
				// See #586 (and #335) modifications need more than a read lock
				// 緩存的value對象沒有被回收,且此次訪問到了,則把此對象引用加入強引用集合
				// 使其不會被回收
				synchronized (hardLinksToAvoidGarbageCollection) {
					hardLinksToAvoidGarbageCollection.addFirst(result);
					if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
						hardLinksToAvoidGarbageCollection.removeLast();
					}
				}
			}
		}
		return result;
	}

WeakCache弱引用緩存裝飾器:

WeakCache在實現上與SoftCache幾乎相同,只是把引用對象由SoftReference軟引用換成了WeakReference弱引用。


最後,介紹特殊的緩存裝飾器——TransactionalCache事務性緩存

TransactionalCache比其餘Cache對象多出了2個方法:commit()和rollback()。TransactionalCache對象內部存在暫存區,全部對緩存對象的寫操做都不會直接做用於緩存對象,而是被保存在暫存區,只有調用TransactionalCache的commit()方法時,全部的更新操做纔會真正同步到緩存對象中。

這樣的話,就會存在一個問題:

若是事務被設置爲自動提交(autoCommit=true)的話,寫操做會更新RDBMS(關係型數據庫管理系統),但不會清空緩存對象(由於自動提交不會調用commit方法),這樣會產生數據庫與緩存中數據不一致的狀況。若是緩存沒有過時失效的機制,那麼問題會很嚴重。

TransactionalCache數據結構以下:

// 原始的被代理的Cache緩存對象
	private Cache delegate;
	// 調用commit()方法時是否清空緩存,初始爲false
	// 若是此值爲true,則調用commit時會進行清空緩存的操做
	// 只有事務中包含更新操做時,此值纔會爲true
	// 不然只須要覆蓋指定key/value的更新便可,(覆蓋分爲刪除和添加兩步操做)
	private boolean clearOnCommit;
	// 在commit時須要進行的添加操做
	// 調用putObject方法時添加到這裏
	private Map<Object, AddEntry> entriesToAddOnCommit;
	// 在commit時須要進行的移除操做,
	// 調用removeObject時添加到這裏
	private Map<Object, RemoveEntry> entriesToRemoveOnCommit;

之因此把putObject操做分爲刪除和添加兩步,我想多是由於有的緩存的添加邏輯是:若是key已存在,則不容許添加,拋出異常。


到這裏,mybatis內部Cache接口及其全部實現類都已解析完畢。

緩存體系中還有另外3個組件,分別是CacheKey、TransactionalCacheManager和CacheBuilder,會在後續陸續介紹。

相關文章
相關標籤/搜索