zoukankan      html  css  js  c++  java
  • oscache源码浅析

      oscache作为本地缓存框架,存储模型依然是通用的缓存键值对模型。oscache使用HashTable存放数据,我们看下源码:

      GeneralCacheAdministrator:

        /**
         * Get an object from the cache
         *
         * @param key             The key entered by the user.
         * @param refreshPeriod   How long the object can stay in cache in seconds. To
         * allow the entry to stay in the cache indefinitely, supply a value of
         * {@link CacheEntry#INDEFINITE_EXPIRY}
         * @return   The object from cache
         * @throws NeedsRefreshException when no cache entry could be found with the
         * supplied key, or when an entry was found but is considered out of date. If
         * the cache entry is a new entry that is currently being constructed this method
         * will block until the new entry becomes available. Similarly, it will block if
         * a stale entry is currently being rebuilt by another thread and cache blocking is
         * enabled (<code>cache.blocking=true</code>).
         */
        public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException {
            return getCache().getFromCache(key, refreshPeriod);
        }

      Cache:

        /**
         * Retrieve an object from the cache specifying its key.
         *
         * @param key             Key of the object in the cache.
         * @param refreshPeriod   How long before the object needs refresh. To
         * allow the object to stay in the cache indefinitely, supply a value
         * of {@link CacheEntry#INDEFINITE_EXPIRY}.
         * @param cronExpiry      A cron expression that specifies fixed date(s)
         *                        and/or time(s) that this cache entry should
         *                        expire on.
         *
         * @return The object from cache
         *
         * @throws NeedsRefreshException Thrown when the object either
         * doesn't exist, or exists but is stale. When this exception occurs,
         * the CacheEntry corresponding to the supplied key will be locked
         * and other threads requesting this entry will potentially be blocked
         * until the caller repopulates the cache. If the caller choses not
         * to repopulate the cache, they <em>must</em> instead call
         * {@link #cancelUpdate(String)}.
         */
        public Object getFromCache(String key, int refreshPeriod, String cronExpiry) throws NeedsRefreshException {
            CacheEntry cacheEntry = this.getCacheEntry(key, null, null);
    
            Object content = cacheEntry.getContent();
            CacheMapAccessEventType accessEventType = CacheMapAccessEventType.HIT;
    
            boolean reload = false;
    
            // Check if this entry has expired or has not yet been added to the cache. If
            // so, we need to decide whether to block, serve stale content or throw a
            // NeedsRefreshException
            if (this.isStale(cacheEntry, refreshPeriod, cronExpiry)) {
    
                //Get access to the EntryUpdateState instance and increment the usage count during the potential sleep
                EntryUpdateState updateState = getUpdateState(key);
                try {
                    synchronized (updateState) {
                        if (updateState.isAwaitingUpdate() || updateState.isCancelled()) {
                            // No one else is currently updating this entry - grab ownership
                            updateState.startUpdate();
                            
                            if (cacheEntry.isNew()) {
                                accessEventType = CacheMapAccessEventType.MISS;
                            } else {
                                accessEventType = CacheMapAccessEventType.STALE_HIT;
                            }
                        } else if (updateState.isUpdating()) {
                            // Another thread is already updating the cache. We block if this
                            // is a new entry, or blocking mode is enabled. Either putInCache()
                            // or cancelUpdate() can cause this thread to resume.
                            if (cacheEntry.isNew() || blocking) {
                                do {
                                    try {
                                        updateState.wait();
                                    } catch (InterruptedException e) {
                                    }
                                } while (updateState.isUpdating());
                                
                                if (updateState.isCancelled()) {
                                    // The updating thread cancelled the update, let this one have a go. 
                                    // This increments the usage count for this EntryUpdateState instance
                                    updateState.startUpdate();
                                    
                                    if (cacheEntry.isNew()) {
                                        accessEventType = CacheMapAccessEventType.MISS;
                                    } else {
                                        accessEventType = CacheMapAccessEventType.STALE_HIT;
                                    }
                                } else if (updateState.isComplete()) {
                                    reload = true;
                                } else {
                                    log.error("Invalid update state for cache entry " + key);
                                }
                            }
                        } else {
                            reload = true;
                        }
                    }
                } finally {
                    //Make sure we release the usage count for this EntryUpdateState since we don't use it anymore. If the current thread started the update, then the counter was
                    //increased by one in startUpdate()
                    releaseUpdateState(updateState, key);
                }
            }
    
            // If reload is true then another thread must have successfully rebuilt the cache entry
            if (reload) {
                cacheEntry = (CacheEntry) cacheMap.get(key);
    
                if (cacheEntry != null) {
                    content = cacheEntry.getContent();
                } else {
                    log.error("Could not reload cache entry after waiting for it to be rebuilt");
                }
            }
    
            dispatchCacheMapAccessEvent(accessEventType, cacheEntry, null);
    
            // If we didn't end up getting a hit then we need to throw a NRE
            if (accessEventType != CacheMapAccessEventType.HIT) {
                throw new NeedsRefreshException(content);
            }
    
            return content;
        }

      继续进入getCacheEntry方法:

    /**
         * Get an entry from this cache or create one if it doesn't exist.
         *
         * @param key    The key of the cache entry
         * @param policy Object that implements refresh policy logic
         * @param origin The origin of request (optional)
         * @return CacheEntry for the specified key.
         */
        protected CacheEntry getCacheEntry(String key, EntryRefreshPolicy policy, String origin) {
            CacheEntry cacheEntry = null;
    
            // Verify that the key is valid
            if ((key == null) || (key.length() == 0)) {
                throw new IllegalArgumentException("getCacheEntry called with an empty or null key");
            }
    
            cacheEntry = (CacheEntry) cacheMap.get(key);
    
            // if the cache entry does not exist, create a new one
            if (cacheEntry == null) {
                if (log.isDebugEnabled()) {
                    log.debug("No cache entry exists for key='" + key + "', creating");
                }
    
                cacheEntry = new CacheEntry(key, policy);
            }
    
            return cacheEntry;
        }

      跟到这里,终于出现正主cacheMap:

        /**
         * The actual cache map. This is where the cached objects are held.
         */
        private AbstractConcurrentReadCache cacheMap = null;

      我们看下这个类AbstractConcurrentReadCache:

    /**
     * A version of Hashtable that supports mostly-concurrent reading, but exclusive writing.
     * Because reads are not limited to periods
     * without writes, a concurrent reader policy is weaker than a classic
     * reader/writer policy, but is generally faster and allows more
     * concurrency. This class is a good choice especially for tables that
     * are mainly created by one thread during the start-up phase of a
     * program, and from then on, are mainly read (with perhaps occasional
     * additions or removals) in many threads.  If you also need concurrency
     * among writes, consider instead using ConcurrentHashMap.
     * <p>
     *
     * Successful retrievals using get(key) and containsKey(key) usually
     * run without locking. Unsuccessful ones (i.e., when the key is not
     * present) do involve brief synchronization (locking).  Also, the
     * size and isEmpty methods are always synchronized.
     *
     * <p> Because retrieval operations can ordinarily overlap with
     * writing operations (i.e., put, remove, and their derivatives),
     * retrievals can only be guaranteed to return the results of the most
     * recently <em>completed</em> operations holding upon their
     * onset. Retrieval operations may or may not return results
     * reflecting in-progress writing operations.  However, the retrieval
     * operations do always return consistent results -- either those
     * holding before any single modification or after it, but never a
     * nonsense result.  For aggregate operations such as putAll and
     * clear, concurrent reads may reflect insertion or removal of only
     * some entries. In those rare contexts in which you use a hash table
     * to synchronize operations across threads (for example, to prevent
     * reads until after clears), you should either encase operations
     * in synchronized blocks, or instead use java.util.Hashtable.
     *
     * <p>
     *
     * This class also supports optional guaranteed
     * exclusive reads, simply by surrounding a call within a synchronized
     * block, as in <br>
     * <code>AbstractConcurrentReadCache t; ... Object v; <br>
     * synchronized(t) { v = t.get(k); } </code> <br>
     *
     * But this is not usually necessary in practice. For
     * example, it is generally inefficient to write:
     *
     * <pre>
     *   AbstractConcurrentReadCache t; ...            // Inefficient version
     *   Object key; ...
     *   Object value; ...
     *   synchronized(t) {
     *     if (!t.containsKey(key))
     *       t.put(key, value);
     *       // other code if not previously present
     *     }
     *     else {
     *       // other code if it was previously present
     *     }
     *   }
     *</pre>
     * Instead, just take advantage of the fact that put returns
     * null if the key was not previously present:
     * <pre>
     *   AbstractConcurrentReadCache t; ...                // Use this instead
     *   Object key; ...
     *   Object value; ...
     *   Object oldValue = t.put(key, value);
     *   if (oldValue == null) {
     *     // other code if not previously present
     *   }
     *   else {
     *     // other code if it was previously present
     *   }
     *</pre>
     * <p>
     *
     * Iterators and Enumerations (i.e., those returned by
     * keySet().iterator(), entrySet().iterator(), values().iterator(),
     * keys(), and elements()) return elements reflecting the state of the
     * hash table at some point at or since the creation of the
     * iterator/enumeration.  They will return at most one instance of
     * each element (via next()/nextElement()), but might or might not
     * reflect puts and removes that have been processed since they were
     * created.  They do <em>not</em> throw ConcurrentModificationException.
     * However, these iterators are designed to be used by only one
     * thread at a time. Sharing an iterator across multiple threads may
     * lead to unpredictable results if the table is being concurrently
     * modified.  Again, you can ensure interference-free iteration by
     * enclosing the iteration in a synchronized block.  <p>
     *
     * This class may be used as a direct replacement for any use of
     * java.util.Hashtable that does not depend on readers being blocked
     * during updates. Like Hashtable but unlike java.util.HashMap,
     * this class does NOT allow <tt>null</tt> to be used as a key or
     * value.  This class is also typically faster than ConcurrentHashMap
     * when there is usually only one thread updating the table, but
     * possibly many retrieving values from it.
     * <p>
     *
     * Implementation note: A slightly faster implementation of
     * this class will be possible once planned Java Memory Model
     * revisions are in place.
     *
     * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
     **/
    public abstract class AbstractConcurrentReadCache extends AbstractMap implements Map, Cloneable, Serializable

      类注释说明AbstractConcurrentReadCache是一个HashTable的版本,支持读多写少的应用场景,基本上缓存都是用户读多写少的场景。接下来我们再看下oscache怎么实例化的。还是从GeneralCacheAdministrator入手:

        /**
         * Create the cache administrator.
         */
        public GeneralCacheAdministrator() {
            this(null);
        }
    
        /**
         * Create the cache administrator with the specified properties
         */
        public GeneralCacheAdministrator(Properties p) {
            super(p);
            log.info("Constructed GeneralCacheAdministrator()");
            createCache();
        }

      它的构造函数调用了父类AbstractCacheAdministrator的构造函数:

        /**
         * Create the AbstractCacheAdministrator.
         *
         * @param p the configuration properties for this cache.
         */
        protected AbstractCacheAdministrator(Properties p) {
            loadProps(p);
            initCacheParameters();
    
            if (log.isDebugEnabled()) {
                log.debug("Constructed AbstractCacheAdministrator()");
            }
        }

      我们看它怎么加载配置文件的:

        /**
         * Load the properties file from the classpath.
         */
        private void loadProps(Properties p) {
            config = new Config(p);
        }

      进入Config类:

        /**
         * Create an OSCache configuration with the specified properties.
         * Note that it is the responsibility of the caller to provide valid
         * properties as no error checking is done to ensure that required
         * keys are present. If you're unsure of what keys should be present,
         * have a look at a sample oscache.properties file.
         *
         * @param p The properties to use for this configuration. If null,
         * then the default properties are loaded from the <code>oscache.properties</code>
         * file.
         */
        public Config(Properties p) {
            if (log.isDebugEnabled()) {
                log.debug("OSCache: Config called");
            }
    
            if (p == null) {
                this.properties = loadProperties(PROPERTIES_FILENAME, "the default configuration");
            } else {
                this.properties = p;
            }
        }

      千呼万唤始出来,默认配置文件oscache.properties:

        /**
         * Name of the properties file.
         */
        private final static String PROPERTIES_FILENAME = "/oscache.properties";
  • 相关阅读:
    泛型的内部原理:类型擦除以及类型擦除带来的问题
    Redis的那些最常见面试问题
    线程池全面解析
    对线程调度中Thread.sleep(0)的深入理解
    集群环境下Redis分布式锁
    3.8
    3.7
    3.6任务
    3.5任务
    3.4
  • 原文地址:https://www.cnblogs.com/wuxun1997/p/8668952.html
Copyright © 2011-2022 走看看