分享

ehcache缓存淘汰浅析

 碧海山城 2012-12-16

最近要设计个缓存系统,所以到处看了看,简单看了ehcache,不得不感叹源码太多了,能写成这样也服了Y,还是像googleguava看齐吧,当然ehcache号称支持分布式

简单使用

记录下简单使用,主要是看源码的时候可以找到入口

http://passover.blog.51cto.com/2431658/486709

<?xml version="1.0" encoding="UTF-8"?> 

<ehcache> 

 

    <diskStore path="java.io.tmpdir" /> 

     

    <defaultCache  

        maxElementsInMemory="10000"  

        eternal="false" 

        timeToIdleSeconds="120"  

        timeToLiveSeconds="120"  

        overflowToDisk="true" 

        diskPersistent="false"  

        diskExpiryThreadIntervalSeconds="120" 

        memoryStoreEvictionPolicy="LRU"  

    /> 

         

    <cache name="sample"          

        maxElementsInMemory="5"   //缓存中允许创建的最大对象数

        maxElementsOnDisk = "5"  

        eternal="false"    //缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。

        timeToIdleSeconds="1440"   //缓存数据的钝化时间,也就是在一个元素消亡之前,两次访问时间的最大时间间隔值

        diskPersistent="false"  

        timeToLiveSeconds="2880"   //缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值

        overflowToDisk="false"   //内存不足时,是否启用磁盘缓存

        memoryStoreEvictionPolicy="FIFO"   //缓存满了之后的淘汰算法

        statistics="true"  

    /> 

 

</ehcache> 

public class EhcacheTest { 

     

    private static final Logger logger = Logger.getLogger(EhcacheTest.class); 

     

    private static Cache sampleCache = null; 

     

    public static void main(String[] args) { 

        init(); 

        test(); 

    } 

 

    private static void test() { 

        logger.info(sampleCache.getMemoryStoreEvictionPolicy()); 

        for(int i=0; i<10; i++){ 

            //写入缓存 

            sampleCache.put(new Element(i, "v" + i)); 

            //打印当前缓存的所有值 

            logger.info(sampleCache.getKeys()); 

            //读取缓存 

            Element e = sampleCache.get(i); 

            logger.info(e.getValue()); 

        } 

        //打印命中统计 

        logger.info(sampleCache.getStatistics()); 

    } 

     

    private static void init() { 

        CacheManager manager = CacheManager.create(); 

//      manager.addCache("sample"); //已经在配置文件定义过了 

        sampleCache = manager.getCache("sample"); 

    } 

 

}

另一种使用方式:

String name = "Namespace";

        int capacity = 500;

        int refreshPeriod = 5000;

        // Initialize EhCache

        Cache cache = new Cache(name, capacity, false, false, refreshPeriod, 0);

        cache.initialise();

        System.out.println(

            "Initialize EhCache: " +

            "   name:       " + name +

            "   capacity:   " + capacity +

            "   expire:     " + refreshPeriod

        );

        // Set data into EhCache

        String key1 = "Key";

        String value1 = "Value";

        Element element1 = new Element(key1, value1);

        cache.put(element1);

        System.out.println("Set (" + key1 + ", " + value1 + ") into EhCache.");

        // Get data from EhCache

        Element element2 = cache.get(key1);

        String key2 = (String) element2.getObjectKey();

        String value2 = (String) element2.getObjectValue();

        System.out.println("Get (" + key2 + ", " + value2 + ") from EhCache.");

        // Remove data from EhCache

        if (cache.remove(key2)) {

            System.out.println("Remove data with key = " + key2 + " successfully.");

        }

        // Get EhCache size

        System.out.println("EhCache size is " + cache.getSize());

最重要的入口就是CacheManagerCacheManager起到类似工厂的作用,主要还是看Cache

CacheManager

CacheManager,默认会去找ehcache.xml以及ehcache-failsafe.xml等文件,最后创建的是一个Cache对象

private void addConfiguredCaches(ConfigurationHelper configurationHelper) {

// 根据配置创建缓存Cache对象        

Set unitialisedCaches = configurationHelper.createCaches();

        for (Iterator iterator = unitialisedCaches.iterator(); 

iterator.hasNext();) {

            Ehcache unitialisedCache = (Ehcache) iterator.next();

    //调用Cache的init方法

            addCacheNoCheck(unitialisedCache, true);

            ......

            }

        }

    }

============================================================

//在Cache的init方法中,会根据不同的淘汰策略,选择不同的Store

if (useClassicLru && 

configuration.getMemoryStoreEvictionPolicy().equals(

                MemoryStoreEvictionPolicy.LRU)) {

//老的LRU策略,选择LruMemoryStore

        Store disk = createDiskStore();

        store = new LegacyStoreWrapper(new LruMemoryStore(this, disk), disk,  registeredEventListeners, configuration);

} else {

//可以存到磁盘,DiskBackedMemoryStore(Memory和DiskStore的整合)

//新的淘汰机制 貌似会有些问题

http:///questions/2310818/ehcache-lru-evicting-recently-used-entries

        if (configuration.isOverflowToDisk()) {

            store = DiskBackedMemoryStore.create(this, onHeapPool, onDiskPool);

        } else {

    //只在内存上,MemoryOnlyStore

            store = MemoryOnlyStore.create(this, onHeapPool);

        }

    }

store可以有很多实现,包括DiskBackedMemory(使用DiskStore)或者MemoryOnlyStore(使用MemoryStore)或者

LocalTransactionStore/JtaLocalTransactionStore之类的,分布式的靠Listener去实现,cache的put、update、remove、evict等操作都会发出事件,然后由listener去实现,比如RMISynchronousCacheReplicator

这些store的实现都和ConcurrentHashMap类似,也是分段,默认分64个段64个锁

淘汰机制

LFU 最不经常使用,计算频率,也就是get的次数

FIFO 先进先出,计算下写入时间

LRU 最近最少使用,有些使用链表,有些只是记录一个最后访问时间

是否要淘汰都是根据最大容量,超过最大容量后,就会根据指定的策略淘汰数据,先看看新的淘汰算法吧

3.1 Store

主要看看MemoryStore,在Store内部,有一个SelectableConcurrentHashMap,和ConcurrentHashMap很像,也是采用分段机制,但是他的淘汰机制好像非常不好没有使用链表的形式,而是在所有元素中遍历,

//先找到要淘汰的个数

int evict = Math.min(map.quickSize() - maximumSize, MAX_EVICTION_RATIO);

for (int i = 0; i < evict; i++) {

//每次随机选出几个,再确定最应该淘汰的一个,效率不高 removeElementChosenByEvictionPolicy(elementJustAdded);

}

public Element[] getRandomValues(final int size, Object keyHint) {

        ArrayList<Element> sampled = new ArrayList<Element>(size * 2);

 // pick a random starting point in the map

        int randomHash = rndm.nextInt();

        final int segmentStart;

        if (keyHint == null) {

            segmentStart = (randomHash >>> segmentShift) & segmentMask;

        } else {

            segmentStart = (hash(keyHint.hashCode()) >>> segmentShift) & segmentMask;

        }

        int segmentIndex = segmentStart;

        do {

            final HashEntry[] table = segments[segmentIndex].table;

            final int tableStart = randomHash & (table.length - 1);

            int tableIndex = tableStart;

            do {

                for (HashEntry e = table[tableIndex]; e != null; e = e.next) {

                    Element value = e.value;

                    if (value != null && (!(e.pinned && elementPinningEnabled) || value.isExpired())) {

                        sampled.add(value);

                    }

                }

                if (sampled.size() >= size) {

                    return sampled.toArray(new Element[sampled.size()]);

                }

                //move to next table slot

                tableIndex = (tableIndex + 1) & (table.length - 1);

            } while (tableIndex != tableStart);

            //move to next segment

            segmentIndex = (segmentIndex + 1) & segmentMask;

        } while (segmentIndex != segmentStart);

        return sampled.toArray(new Element[sampled.size()]);

    }

整了半天,貌似是为了随机定位到某个段,然后随机从某个元素开始遍历,直到找到指定个数的需要淘汰的数量,没仔细去想并发问题了,觉得效率比较低,不管了,还是

着重看下他的淘汰策略吧:

public Element selectedBasedOnPolicy(Element[] sampledElements, Element justAdded) {

        //edge condition when Memory Store configured to size 0

        if (sampledElements.length == 1) {

            return sampledElements[0];

        }

        Element lowestElement = null;

        for (Element element : sampledElements) {

            if (element == null) {

                continue;

            }

            if (lowestElement == null) {

                if (!element.equals(justAdded)) {

                    lowestElement = element;

                }

            } else if (

compare(lowestElement, 

element) && !element.equals(justAdded)) {

                lowestElement = element;

            }

        }

        return lowestElement;

    }

LRU LFU FIFO的核心就在对compare的实现,

LRU:比较最后访问时间

public boolean compare(Element element1, Element element2) {

   return element2.getLastAccessTime() < element1.getLastAccessTime();

}

LFU:比较get次数(在get的时候会累加)

public boolean compare(Element element1, Element element2) {

   return element2.getHitCount() < element1.getHitCount();

}

FIFO:创建/修改时间

public boolean compare(Element element1, Element element2) {

    return element2.getLatestOfCreationAndUpdateTime() < 

     element1.getLatestOfCreationAndUpdateTime();

}

3.2 老的LruMemoryStore

新的那种随机定位的方式让人感觉很怪,老的LRU实际使用的是SpoolingLinkedHashMap,

他在LinkedHashMap上增加了,他的put/get/remove是synchronized的,,所以没有并发安全性问题,但是感觉效率也不太高。。就是LinkedHashMap的同步版本了

总结

没有发现啥亮点,代码太多了,看的几部分也没啥参考价值。。。。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多