最近要设计个缓存系统,所以到处看了看,简单看了ehcache,不得不感叹源码太多了,能写成这样也I 服了Y,还是像google的guava看齐吧,当然ehcache号称支持分布式 1 简单使用记录下简单使用,主要是看源码的时候可以找到入口 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()); 最重要的入口就是CacheManager和Cache,Manager起到类似工厂的作用,主要还是看Cache 2 CacheManagerCacheManager,默认会去找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个锁 3 淘汰机制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的同步版本了 4 总结没有发现啥亮点,代码太多了,看的几部分也没啥参考价值。。。。 |
|