After all, the West Lake in June, the scenery is not the same as the four.

Next day lotus leaves infinite bi, reflecting the day lotus red.

Xiao Out jingci Temple to send Lin Zifang – Yang Wanli

Weekend with a small partner about a boxihu, this time lotus bloom just… Before starting the article, put a beautiful picture of “Buddha” to the town building!!

Recently, I have used Google’s Guava and sealed a caching template scheme by myself, which is hereby recorded for future needs.

A GC problem with a cache timed cleanup task

Why do we start with that? Because if we don’t do that, guava is done!

Recently, the project needs to use the cache to do the cache processing for the frequently queried data. First of all, we do not want to introduce third-party services such as Redis or memcache. Second, we do not have high requirements for data consistency, and there is no need for query interfaces in the cluster to share a cache of data. So all we have to do is implement a memory-based cache.

I didn’t initially consider using Guava for this, but instead wrote a CurrentHashMap based caching scheme; It is important to be clear here, because caching in this scenario is intended to provide time-out clearing capability, and therefore the ability to periodically clear expired data has been added to the cache framework.

Here I’ll just put up the code with the timing clear:

 /** * static inner class to handle timeout */
private class ClearCacheThread extends Thread {
    @Override
    public void run(a) {
        while (true) {try {
                long now = System.currentTimeMillis();
                Object[] keys = map.keySet().toArray();
                for (Object key : keys) {
                    CacheEntry entry = map.get(key);
                    if (now - entry.time >= cacheTimeout) {
                        synchronized (map) {
                            map.remove(key);
                            if (LOGGER.isDebugEnabled()){
                                LOGGER.debug("language cache timeout clear");
                            }
                        }
                    }
                }
            }catch (Exception e){
                LOGGER.error("clear out time cache value error;",e); }}}}Copy the code

This thread is used to process expired data separately. When the cache is initialized, the thread’s start method is triggered to start execution.

Because of this code error, the machine GC was triggered at a ridiculous rate after I released the dev environment. After trying different restoration schemes, they finally gave up; Switched to Guava!

Leave a comment below to discuss why there is frequent GC. I will post my conclusions in the comments.

guava

Why guava, it was obviously recommended by the big guy!!

Guava is a memory-based caching toolkit provided by Google. Guava Cache provides a mechanism to Cache key-value pairs of data into local (JVM) memory, suitable for data that is rarely changed. Guava Cache is similar to ConcurrentMap, but not exactly the same. The basic difference is that ConcurrentMap keeps all added elements until they are explicitly removed. In contrast, the Guava Cache is typically set to auto-recycle elements to limit memory footprint.

For our scenario, The capabilities provided by Guava fit our needs:

  • Small data changes
  • Based on the memory
  • It can be recycled automatically

Since we choose it, it is necessary for us to have a general understanding of it; Let’s take a look at some of the classes and interfaces it provides:

Interface/class Explain in detail
Cache 【 I 】; Define operations such as GET, PUT, and invalidate, which only add, delete, and modify the cache, but do not load data.
AbstractCache [C]; Implement the Cache interface. Among them, batch operations are cyclic execution of a single behavior, and the single behavior is not defined.
LoadingCache 【 I 】; Inherited from Cache. Define operations like GET, getUnchecked, and getAll, which load data from the data source.
AbstractLoadingCache [C]; AbstractCache, implements LoadingCache interface.
LocalCache [C]; The core class of the entire Guava cache contains the data structure of the Guava cache and the basic operation methods of the cache.
LocalManualCache [C]; LocalCache internal static class that implements the Cache interface. The internal add, delete, or change cache operations all call the corresponding methods of the member variable localCache (type localCache).
LocalLoadingCache [C]; LocalCache internal static class, inherited from the LocalManualCache class, implements the LoadingCache interface. All of its operations are also methods that call the member variable localCache (of type localCache)
CacheBuilder [C]; Cache builder. Entry to build the cache, specify the cache configuration parameters, and initialize the local cache. The CacheBuilder passes all of the previously set parameters to LocalCache in the build method, without actually participating in any calculations of its own
CacheLoader [C]; Used to load data from data sources and define operations such as load, reload, and loadAll.

The core of Guava is probably the LocalCache class.

@GwtCompatible(emulated = true)
class LocalCache<K.V> extends AbstractMap<K.V> implements
ConcurrentMap<K.V> 
Copy the code

On the source of this class here is not detailed, directly look at the actual application of my packaging ideas [packaging to meet my current needs, if there is a small partner need to learn, you can do their own extension]

private static final int            MAX_SIZE     = 1000;
private static final int            EXPIRE_TIME  = 10;
private static final int            DEFAULT_SIZE = 100;

private int                         maxSize      = MAX_SIZE;
private int                         expireTime   = EXPIRE_TIME;
/** Time unit (min) */
private TimeUnit                    timeUnit     = TimeUnit.MINUTES;
/** Cache initialization or reset time */
private Date                        resetTime;

/** Record the maximum number of historical caches and point in time */
private long                        highestSize  = 0;
private Date                        highestTime;

private volatile LoadingCache<K, V> cache;
Copy the code

Here we define some constants and basic property information, which of course provide set&GET methods that we can set ourselves when we use them.

public LoadingCache<K, V> getCache(a) {
    // Use double check locks to ensure only one cache instance
    if(cache == null) {synchronized (this) {
            if(cache == null) {// The constructor of CacheBuilder is private and an instance of CacheBuilder can only be obtained through its static method newBuilder()
                cache = CacheBuilder.newBuilder()
                        // Set the initial capacity of the cache container to 100
                        .initialCapacity(DEFAULT_SIZE)
                        // The largest entry for cached data
                        .maximumSize(maxSize)
                        // Timed reclaim: The cache item is reclaimed if it has not been written (created or overwritten) within a given time.
                        .expireAfterWrite(expireTime, timeUnit)
                        // Enable statistics -> Statistics cache hit ratio, etc
                        .recordStats()
                        // Set cache removal notification
                        .removalListener((notification)-> {
                            if (LOGGER.isDebugEnabled()){
                                LOGGER.debug("{} was removed, cause is {}" ,notification.getKey(), notification.getCause());
                            }
                        })
                        .build(new CacheLoader<K, V>() {
                            @Override
                            public V load(K key) throws Exception {
                                returnfetchData(key); }});this.resetTime = new Date();
                this.highestTime = new Date();
                if (LOGGER.isInfoEnabled()){
                    LOGGER.info("Local cache {} initialized successfully.".this.getClass().getSimpleName()); }}}}return cache;
}
Copy the code

The above code is the core of the cache, and we use this code to generate our cache object. See the comments for specific attribute parameters.

AbstractGuavaCache is an abstract class, so I’ve added CacheManger to manage caches and provide a specific interface. In CacheManger, I used a static inner class to create the current default cache.

/** * Implements a default cache using static inner classes that delegate to the Manager ** DefaultGuavaCache uses a simple singleton pattern *@param <String>
 * @param <Object>
 */
private static class DefaultGuavaCache<String.Object> extends
AbstractGuavaCache<String.Object> {

    private static AbstractGuavaCache cache = new DefaultGuavaCache();

    /** * handle auto load cache, load * here * as needed@param key
     * @return* /
    @Override
    protected Object fetchData(String key) {
        return null;
    }

    public static AbstractGuavaCache getInstance(a) {
        returnDefaultGuavaCache.cache; }}Copy the code

If you want to extend AbstractGuavaCache, you just need to extend AbstractGuavaCache. The specific code is posted below.

Two complete classes

AbstractGuavaCache

public abstract class AbstractGuavaCache<K.V> {

    protected final Logger              LOGGER       = LoggerFactory.getLogger(AbstractGuavaCache.class);

    private static final int            MAX_SIZE     = 1000;
    private static final int            EXPIRE_TIME  = 10;
    /** The parameter used to initialize the cache and its default value */
    private static final int            DEFAULT_SIZE = 100;

    private int                         maxSize      = MAX_SIZE;

    private int                         expireTime   = EXPIRE_TIME;
    /** Time unit (min) */
    private TimeUnit                    timeUnit     = TimeUnit.MINUTES;
    /** Cache initialization or reset time */
    private Date                        resetTime;

    /** Record the maximum number of historical caches and point in time */
    private long                        highestSize  = 0;
    private Date                        highestTime;

    private volatile LoadingCache<K, V> cache;

    public LoadingCache<K, V> getCache(a) {
        // Use double check locks to ensure only one cache instance
        if(cache == null) {synchronized (this) {
                if(cache == null) {// The CacheBuilder constructor is private and can only be accessed through its static method ne
                    //wBuilder() to get an instance of CacheBuilder
                    cache = CacheBuilder.newBuilder()
                            // Set the initial capacity of the cache container to 100
                            .initialCapacity(DEFAULT_SIZE)
                            // The largest entry for cached data
                            .maximumSize(maxSize)
                            // Timed reclaim: The cache entry was not written for a given period of time
                            // (create or overwrite), then recycle.
                            .expireAfterWrite(expireTime, timeUnit)
                            // Enable statistics -> Statistics cache hit ratio, etc
                            .recordStats()
                            // Set cache removal notification
                            .removalListener((notification)-> {
                                if (LOGGER.isDebugEnabled()){
                                   / /...
                                }
                            })
                            .build(new CacheLoader<K, V>() {
                                @Override
                                public V load(K key) throws Exception {
                                    returnfetchData(key); }});this.resetTime = new Date();
                    this.highestTime = new Date();
                    if (LOGGER.isInfoEnabled()){
                         / /...}}}}return cache;
    }

    /** * Gets a value from a database or other data source based on the key and is automatically saved in the cache. The * * modification method is a template method, and subclasses need to implement * *@param key
     * @returnValue, along with the key, is loaded into the cache. * /
    protected abstract V fetchData(K key);

    /** * fetchData from the cache (the first automatic call to fetchData to fetchData from outside) and handle exceptions *@param key
     * @return Value
     * @throws ExecutionException
     */
    protected V getValue(K key) throws ExecutionException {
        V result = getCache().get(key);
        if (getCache().size() > highestSize) {
            highestSize = getCache().size();
            highestTime = new Date();
        }
        return result;
    }

    public int getMaxSize(a) {
        return maxSize;
    }

    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    public int getExpireTime(a) {
        return expireTime;
    }

    public void setExpireTime(int expireTime) {
        this.expireTime = expireTime;
    }

    public TimeUnit getTimeUnit(a) {
        return timeUnit;
    }

    public void setTimeUnit(TimeUnit timeUnit) {
        this.timeUnit = timeUnit;
    }

    public Date getResetTime(a) {
        return resetTime;
    }

    public void setResetTime(Date resetTime) {
        this.resetTime = resetTime;
    }

    public long getHighestSize(a) {
        return highestSize;
    }

    public void setHighestSize(long highestSize) {
        this.highestSize = highestSize;
    }

    public Date getHighestTime(a) {
        return highestTime;
    }

    public void setHighestTime(Date highestTime) {
        this.highestTime = highestTime; }}Copy the code

DefaultGuavaCacheManager

public class DefaultGuavaCacheManager {

    private static final Logger  LOGGER =
    LoggerFactory.getLogger(DefaultGuavaCacheManager.class);
   // Cache the wrapper class
    private static AbstractGuavaCache<String, Object> cacheWrapper;

    /** * Initializes the cache container */
    public static boolean initGuavaCache(a) {
        try {
            cacheWrapper = DefaultGuavaCache.getInstance();
            if(cacheWrapper ! =null) {
                return true; }}catch (Exception e) {
            LOGGER.error("Failed to init Guava cache;", e);
        }
        return false;
    }

    public static void put(String key, Object value) {
        cacheWrapper.getCache().put(key, value);
    }

    /** * specifies the cache age *@param key
     */
    public static void invalidate(String key) {
        cacheWrapper.getCache().invalidate(key);
    }

    /** * Batch clear *@param keys
     */
    public static void invalidateAll(Iterable
        keys) {
        cacheWrapper.getCache().invalidateAll(keys);
    }

    /** * clear all cached entries: use */ with caution
    public static void invalidateAll(a) {
        cacheWrapper.getCache().invalidateAll();
    }

    public static Object get(String key) {
        try {
            return cacheWrapper.getCache().get(key);
        } catch (Exception e) {
            LOGGER.error("Failed to get value from guava cache;", e);
        }
        return null;
    }

    /** * Implements a default cache using static inner classes that delegate to the Manager ** DefaultGuavaCache uses a simple singleton pattern *@param <String>
     * @param <Object>
     */
    private static class DefaultGuavaCache<String.Object> extends
    AbstractGuavaCache<String.Object> {

        private static AbstractGuavaCache cache = new DefaultGuavaCache();

        /** * handle auto load cache, load * as the case may be@param key
         * @return* /
        @Override
        protected Object fetchData(String key) {
            return null;
        }

        public static AbstractGuavaCache getInstance(a) {
            returnDefaultGuavaCache.cache; }}}Copy the code

reference

Google Guava Tutorial (Chinese version)