天天看點

Java Cache-EHCache系列之Store實作

寫了那麼多,終于到store了。store是ehcache中element管理的核心,所有的element都存放在store中,也就是說store用于所有和element相關的處理。

ehcache中的element

在ehcache中,它将所有的鍵值對抽象成一個element,作為面向對象的設計原則,把資料和操作放在一起,element除了包含key、value屬性以外,它還加入了其他和一個element相關的統計、配置資訊以及操作:

public class element implements serializable, cloneable {

    //the cache key. 從1.2以後不再強制要求serializable,因為如果隻是作為記憶體緩存,則不需要對它做序列化。ignoresizeof注解表示在做sizeof計算時key會被忽略。

    @ignoresizeof

    private final object key;

    //the value. 從1.2以後不再強制要求serializable,因為如果隻是作為記憶體緩存,則不需要對它做序列化。

    private final object value;

    //version of the element. 這個屬性隻是作為紀錄資訊,ehcache實際代碼中并沒有用到,使用者代碼可以通過它來實作不同版本的處理問題。預設值是1。

    //如果net.sf.ehcache.element.version.auto系統屬性設定為true,則當element加入到cache中時會被更新為目前系統時間。此時,使用者設定的值會丢失。

    private volatile long version;

    //the number of times the element was hit.命中次數,在每次查找到一個element會加1。

    private volatile long hitcount;

    //the amount of time for the element to live, in seconds. 0 indicates unlimited. 即一個element自建立(creationtime)以後可以存活的時間。

    private volatile int timetolive = integer.min_value;

    //the amount of time for the element to idle, in seconds. 0 indicates unlimited. 即一個element自最後一次被使用(min(creationtime,lastaccesstime))以後可以存活的時間。

    private volatile int timetoidle = integer.min_value;

    //pluggable element eviction data instance,它存儲這個element的creationtime、lastaccesstime等資訊,竊以為這個抽取成一個單獨的類沒什麼理由,而且這個類的名字也不好。

    private transient volatile elementevictiondata elementevictiondata;

    //if there is an element in the cache and it is replaced with a new element for the same key, 

    //then both the version number and lastupdatetime should be updated to reflect that. the creation time

    //will be the creation time of the new element, not the original one, so that ttl concepts still work. 在put和replace操作中該屬性會被更新。

    private volatile long lastupdatetime;

    //如果timetolive和timetoidle沒有手動設定,該值為true,此時在計算expired時使用cacheconfiguration中的timetilive、timetoidle的值,否則使用element自身的值。

    private volatile boolean cachedefaultlifespan = true;

    //這個id值用于ehcache内部,但是暫時不知道怎麼用。

    private volatile long id = not_set_id;

    //判斷是否expired,這裡如果timetolive、timetoidle都是integer.min_value時傳回false,當他們都是0時,iseternal傳回true

    public boolean isexpired() {

        if (!islifespanset() || iseternal()) {

            return false;

        }

        long now = system.currenttimemillis();

        long expirationtime = getexpirationtime();

        return now > expirationtime;

    }

    //expirationtime算法:如果timetoidle沒有設定,或設定了,但是該element還沒有使用過,取timetolive計算出的值;如果timetolive沒有設定,則取timetoidle計算出的值,

    //否則,取他們的最小值。

    public long getexpirationtime() {

            return long.max_value;

        long expirationtime = 0;

        long ttlexpiry = elementevictiondata.getcreationtime() + timeutil.tomillis(gettimetolive());

        long mostrecenttime = math.max(elementevictiondata.getcreationtime(), elementevictiondata.getlastaccesstime());

        long ttiexpiry = mostrecenttime + timeutil.tomillis(gettimetoidle());

        if (gettimetolive() != 0 && (gettimetoidle() == 0 || elementevictiondata.getlastaccesstime() == 0)) {

            expirationtime = ttlexpiry;

        } else if (gettimetolive() == 0) {

            expirationtime = ttiexpiry;

        } else {

            expirationtime = math.min(ttlexpiry, ttiexpiry);

        return expirationtime;

    //在将element加入到cache中并且它的timetolive和timetoidle都沒有設定時,它的timetolive和timetoidle會根據cacheconfiguration的值調用這個方法更新。

    protected void setlifespandefaults(int tti, int ttl, boolean eternal) {

        if (eternal) {

            this.timetoidle = 0;

            this.timetolive = 0;

        } else if (iseternal()) {

            this.timetoidle = integer.min_value;

            this.timetolive = integer.min_value;

            timetoidle = tti;

            timetolive = ttl;

}

public class defaultelementevictiondata implements elementevictiondata {

    private long creationtime;

    private long lastaccesstime;

public class cache implements internalehcache, storelistener {

    private void applydefaultstoelementwithoutlifespanset(element element) {

        if (!element.islifespanset()) {

            element.setlifespandefaults(timeutil.converttimetoint(configuration.gettimetoidleseconds()),

                    timeutil.converttimetoint(configuration.gettimetoliveseconds()),

                    configuration.iseternal());

ehcache中的store設計

store是ehcache中用于存儲、管理所有element的倉庫,它抽象出了所有對element在記憶體中以及磁盤中的操作。基本的它可以向一個store中添加element(put、putall、putwithwriter、putifabsent)、從一個store擷取一個或一些element(get、getquiet、getall、getallquiet)、擷取一個store中所有key(getkeys)、從一個store中移除一個或一些element(remove、removeelement、removeall、removewithwriter)、替換一個store中已存儲的element(replace)、pin或unpin一個element(unpinall、ispinned、setpinned)、添加或删除storelistener(addstorelistener、removestorelistener)、擷取一個store的element數量(getsize、getinmemorysize、getoffheapsize、getondisksize、getterracottaclusteredsize)、擷取一個store的element以byte為機關的大小(getinmemorysizeinbytes、getoffheapsizeinbytes、getondisksizeinbytes)、判斷一個key的存在性(containskey、containskeyondisk、containskeyoffheap、containskeyinmemory)、query操作(setattributeextractors、executequery、getsearchattribute)、cluster相關操作(iscachecoherent、isclustercoherent、isnodecoherent、setnodecoherent、waitutilclustercoherent)、其他操作(dispose、getstatus、getmbean、hasabortedsizeof、expireelements、flush、bufferfull、getinmemoryevictionpolicy、setinmemoryevictionpolicy、getinternalcontext、calculatesize)。

所謂cache,就是将部分常用資料緩存在記憶體中,進而提升程式的效率,然而記憶體大小畢竟有限,因而有時候也需要有磁盤加以輔助,因而在ehcache中真正的store實作就兩種(不考慮分布式緩存的情況下):存儲在記憶體中的memorystore和存儲在磁盤中的diskstore,而所有其他store都是給予這兩個store的基礎上來擴充store的功能,如因為記憶體大小的限制,有些時候需要将記憶體中的暫時不用的element寫入到磁盤中,以騰出空間給其他更常用的element,此時就需要memorystore和diskstore共同來完成,這就是frontcachetier做的事情,所有可以結合frontendcachetier一起使用的store都要實作tierablestore接口(diskstore、memorystore、nullstore);對于可控制store占用空間大小做限制的store還可以實作poolablestore(diskstore、memorystore);對于具有terracotta特性的store還實作了terracottastore接口(transactionstore等)。

ehcache中store的設計類結構圖如下:

Java Cache-EHCache系列之Store實作

abstractstore

幾乎所有的store實作都繼承自abstractstore,它實作了query、cluster等相關的接口,但沒有涉及element的管理,而且這部分現在也不了解,不詳述。

memorystore和notifyingmemorystore

memorystore是ehcache中存儲在記憶體中的element的倉庫。它使用selectableconcurrenthashmap作為内部的存儲結構,該類實作參考concurrenthashmap,隻是它加入了pinned、evict等邏輯,不詳述(注:它的setpinned方法中,對不存在的key,會使用一個dummy_pinned_element來建立一個節點,并将它添加到hashentry的鍊中,這時為什麼?竊以為這個應該是為了在以後這個key添加進來後,目前的pinned設定可以對它有影響,因為memorystore中并沒有包含所有的element,還有一部分element是在diskstore中)。而memorystore中的基本實作都代理給selectableconcurrenthashmap,裡面的其他細節在之前的文章中也有說明,不再贅述。 

而notifyingmemorystore繼承自memorystore,它在element evict和exipre時會調用注冊的cacheeventlistener。

diskstore

diskstore依然采用concurrenthashmap的實作思想,因而這部分邏輯不贅述。對diskstore,當一個element添加進來後,需要将其寫入到磁盤中,這是接下來關注的重點。在diskstore中,一個element不再以element本身而存在,而是以disksubstitute的執行個體而存在,disksubstitute有兩個子類:placeholder和diskmarker,當一個element初始被添加到diskstore中時,它是以placeholder的形式存在,當這個placeholder被寫入到磁盤中時,它會轉換成diskmarker。

    public abstract static class disksubstitute {

        protected transient volatile long onheapsize;

        @ignoresizeof

        private transient volatile diskstoragefactory factory;

        disksubstitute(diskstoragefactory factory) {

            this.factory = factory;

        abstract object getkey();

        abstract long gethitcount();

        abstract long getexpirationtime();

        abstract void installed();

        public final diskstoragefactory getfactory() {

            return factory;

    final class placeholder extends disksubstitute {

        private final object key;

        private final element element;

        private volatile boolean failedtoflush;

        placeholder(element element) {

            super(diskstoragefactory.this);

            this.key = element.getobjectkey();

            this.element = element;

        @override

        public void installed() {

            diskstoragefactory.this.schedule(new persistentdiskwritetask(this));

    public static class diskmarker extends disksubstitute implements serializable {

        private final long position;

        private final int size;

        private volatile long hitcount;

        private volatile long expiry;

        diskmarker(diskstoragefactory factory, long position, int size, element element) {

            super(factory);

            this.position = position;

            this.size = size;

            this.hitcount = element.gethitcount();

            this.expiry = element.getexpirationtime();

            //no-op

        void hit(element e) {

            hitcount++;

            expiry = e.getexpirationtime();

當向diskstore添加一個element時,它會先建立一個placeholder,并将該placeholder添加到diskstore中,并在添加完成後調用placeholder的installed()方法,該方法會使用diskstoragefactory schedule一個persistentdiskwritetask,将該placeholder寫入到磁盤(在diskstoragefactory有一個diskwriter線程會在一定的時候執行該task)生成一個diskmarker,釋放placeholder占用的記憶體。在從diskstore移除一個element時,它會先讀取磁盤中的資料,将其解析成element,然後釋放這個element占用的磁盤空間,并傳回這個被移除的element。在從diskstore讀取一個element時,它需要找到diskstore中的disksubstitute,對diskmarker讀取磁盤中的資料,解析成element,然後傳回。 

frontendcachetier 

上述的memorystore和diskstore,他們是各自獨立的,然而cache的一個重要特點是可以将部分記憶體中的資料evict出到磁盤,因為記憶體畢竟是有限的,是以需要有另一個store可以将memorystore和diskstore聯系起來,這就是frontendcachetier做的事情。frontendcachetier有兩個子類:diskbackedmemorystore和memoryonlystore,這兩個類的名字已經能很好的說明他們的用途了,diskbackedmemorystore可以将部分element先evict出到磁盤,它也支援把磁盤檔案作為persistent媒體,在下一次讀取時可以直接從磁盤中的檔案直接讀取并重新建構原來的緩存;而memoryonlystore則隻支援将element存儲在記憶體中。frontendcachetier有兩個store屬性:cache和authority,它将基本上所有的操作都直接同時代理給這兩個store,其中把authority作為主的存儲store,而将cache作為緩存的store。在diskbackedmemorystore中,authority是diskstore,而cache是memorystore,即diskbackedmemorystore将diskstore作為主的存儲store,這剛開始讓我很驚訝,不過仔細想想也是合理的,因為畢竟這裡的disk是作為persistent媒體的;在memoryonlystore中,authority是memorystore,而cache是nullstore。

frontendcachetier在實作get方法時,添加了faults屬性的concurrenthashmap,它是用于多個線程在同時讀取同一key的element時避免多次讀取,每次之前将key和一個fault新執行個體添加到faults中,這樣第二個線程發現已經有另一個線程在讀這個element了,它就可以等待第一個線程讀完直接拿第一個線程讀取的結果即可,以提升性能。

所有作為frontendcachetier的内部store都必須實作tierablestore接口,其中fill、removeifnotpinned、istierpinned、getpresentpinnedkeys為cache store準備,而removenoreturn、ispersistent為authority store準備。

public interface tierablestore extends store {

    void fill(element e);

    boolean removeifnotpinned(object key);

    void removenoreturn(object key);

    boolean istierpinned();

    set getpresentpinnedkeys();

    boolean ispersistent();

lrumemorystore和legacystorewrapper

這兩個store隻是為了相容而存在,其中lrumemorystore使用linkedhashmap作為其存儲結構,他隻支援一種evict算法:lru,這個store的名字也是以而來,其他功能它類似memorystore,而legacystorewrapper則類似frontendcachetier。這兩個store的代碼比較簡單,而且他們也不應該再被使用,因而不細究。 

terraccottastore

對所有實作這個接口的store都還不了解,看以後有沒有時間回來了。。。。