Nothing else to do, hands-on writing a local cache

When learning java concurrency, the book's examples are based caching expanded, so I wanted to write a generic local cache

EDITORIAL

A write cache, the cache needs to consider the underlying storage architecture, cache expires, a cache miss, concurrent read and write and so on, so to write their own local cache will be designed around these points

Cache invalidation

Cache invalidation means that the cache has expired, you need to delete the cache data expired. Delete Delete can be divided into active and passive Erases both

Initiative to remove
  • Timing deleted
    at the same time setting the value of the expiration time, create a timer so that the timer key expiration time comes, perform the removal of the key immediate
    advantages : the memory of the most friendly, guarantee expired key-value pairs as possible is deleted, expired release the memory occupied by the key-value pairs
    drawback : for CPUunfriendly, if the key expired contrast more, delete outdated key-value pairs will occupy a considerable portion of CPUthe execution time
  • Deleted regularly
    from time to time perform a delete expired key operation, and to reduce the delete operation by limiting the duration and frequency of the deletion operations performed CPUaffect the time of [difficult, long and more difficult to set the frequency of execution]
Passive delete
  • Inert delete
    only time will remove the keys on key strengths and weaknesses and regularly delete expired inspection opposite
    advantages : for CPUfriendship
    drawback : memory unfriendly use of inert + Delete to delete a regular basis can be achieved CPUand the balance of memory, so the local cache + delete cache invalidation inert periodically delete two kinds

Cache eliminated

Cache Cache elimination refers to the number reaches a certain value when you delete a data according to certain rules, regardless of whether the data has expired. Common cache elimination algorithm are:

  • FIFO algorithm [ FIFO]
    was first cached data will be the first to be eliminated
  • The most frequently used algorithm [not LFU]
    out using a minimum number of data, it is typically achieved by counting for each data, it is calculated once every time, count the number of minimum-out
  • LRU [ LRU]
    recently used data is not the first to be eliminated, usually achieved through the list, the most recent visit, the newly inserted element moved to the head of the list, out of the list the last element of the local cache selection LRUalgorithm caching eliminated

Cache structure definition

After choosing a cache miss and cache algorithms can determine the cache out of the structure, the original Dynasty is thread-safe K-Vstructure ConcurrentHashMapplus + doubly linked list structure, but how sweet Recently addicted remember English words, aware of the LinkedHashMappossible achieve LRU, lazy use LinkedHashMap. LinkedHashMapBased on insertion order storage [default], [recently read can be on the front, most will not often read on the last According storage access order, the order of insertion sequence memory storage instead simply access the accssOrderset truecan, by default false. At the same time LinkedHashMapprovides a method for determining whether a least frequently necessary to remove the read data [ removeEldestEntry(Map.Entry<K, CacheNode<K, V>> eldest)default return falseis not removed] needs to be removed will be able to override this method

Defined cache node

public class CacheNode<K, V> {
    /**
     * 保存的键
     */
    private K key;

    /**
     * 保存的值
     */
    private V value;

    /**
     * 保存时间
     */
    private long gmtCreate;

    /**
     * 过期时间,单位为毫秒,默认永久有效
     */
    private long expireTime = Long.MAX_VALUE;
}
复制代码

Cache structure initialization

    /**
     * 底层缓存结构
     */
    private LinkedHashMap<K, CacheNode<K, V>> localCache;

    /**
     * 负载因子
     */
    private final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 缓存过期清理策略
     */
    private ExpireStrategy<K, V> lazyExpireStrategy = new LazyExpireStrategy<>();

    private ExpireStrategy<K, V> regularExpireStrategy;

    private int maxCacheSie;

    /**
     * 构造函数
     *
     * @param expireStrategy 缓存失效策略实现类,针对的是定期失效缓存,传入null,定期失效缓存类为默认配置值
     * @param maxCacheSie    缓存最大允许存放的数量,缓存失效策略根据这个值触发
     */
    public LocalCache(int maxCacheSize, ExpireStrategy<K, V> expireStrategy) {
        //缓存最大容量为初始化的大小
        this.maxCacheSize = maxCacheSize;
        //缓存最大容量 => initialCapacity * DEFAULT_LOAD_FACTOR,避免扩容操作
        int initialCapacity = (int) Math.ceil(maxCacheSie / DEFAULT_LOAD_FACTOR) + 1;
        //accessOrder设置为true,根据访问顺序而不是插入顺序
        this.localCache = new LinkedHashMap<K, CacheNode<K, V>>(initialCapacity, DEFAULT_LOAD_FACTOR, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, CacheNode<K, V>> eldest) {
                return size() > maxCacheSie;
            }
        };
        this.regularExpireStrategy = (expireStrategy == null ? new RegularExpireStrategy<>() : expireStrategy);
        //启动定时清除过期键任务
        regularExpireStrategy.removeExpireKey(localCache, null);
    }
复制代码

Description:

  • Rewrite the removeEldestEntrymethod, when the cache size exceeds the set maxCacheSizewill remove elements rarely used
  • Constructor set accessOrderto true, sequential access storage eradicate
  • Size of buffer capacity (int) Math.ceil(maxCacheSie / DEFAULT_LOAD_FACTOR) + 1obtained is calculated, so that even reach the set maxCacheSizewill not trigger operation of the expansion
  • regularExpireStrategy.removeExpireKey(localCache, null);Start periodically delete task

Periodically delete policy implementation:

public class RegularExpireStrategy<K, V> implements ExpireStrategy<K, V> {
    Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * 定期任务每次执行删除操作的次数
     */
    private long executeCount = 100;

    /**
     * 定期任务执行时常 【1分钟】
     */
    private long executeDuration = 1000 * 60;

    /**
     * 定期任务执行的频率
     */
    private long executeRate = 60;

    //get and set
    public long getExecuteCount() {
        return executeCount;
    }

    public void setExecuteCount(long executeCount) {
        this.executeCount = executeCount;
    }

    public long getExecuteDuration() {
        return executeDuration;
    }

    public void setExecuteDuration(long executeDuration) {
        this.executeDuration = executeDuration;
    }

    public long getExecuteRate() {
        return executeRate;
    }

    public void setExecuteRate(long executeRate) {
        this.executeRate = executeRate;
    }

    /**
     * 清空过期Key-Value
     *
     * @param localCache 本地缓存底层使用的存储结构
     * @param key 缓存的键
     * @return 过期的值
     */
    @Override
    public V removeExpireKey(LinkedHashMap<K, CacheNode<K, V>> localCache, K key) {
        logger.info("开启定期清除过期key任务");
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        //定时周期任务,executeRate分钟之后执行,默认1小时执行一次
        executor.scheduleAtFixedRate(new MyTask(localCache), 0, executeRate, TimeUnit.MINUTES);
        return null;
    }

    /**
     * 自定义任务
     */
    private class MyTask<K, V> implements Runnable {
        private LinkedHashMap<K, CacheNode<K, V>> localCache;

        public MyTask(LinkedHashMap<K, CacheNode<K, V>> localCache) {
            this.localCache = localCache;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            List<K> keyList = localCache.keySet().stream().collect(Collectors.toList());
            int size = keyList.size();
            Random random = new Random();

            for (int i = 0; i < executeCount; i++) {
                K randomKey = keyList.get(random.nextInt(size));
                if (localCache.get(randomKey).getExpireTime() - System.currentTimeMillis() < 0) {
                    logger.info("key:{}已过期,进行定期删除key操作", randomKey);
                    localCache.remove(randomKey);
                }

                //超时执行退出
                if (System.currentTimeMillis() - start > executeDuration) {
                    break;
                }
            }
        }
    }
}
复制代码

Description:

  • Use ScheduledExecutorServiceof scheduleAtFixedRaterealization of regular periodic task
  • The default one hour once, each execution time is 1 minute, every attempt to delete 100 random elements [If time permits, key expired]

Delete lazy loading strategy to achieve: LazyExpireStrategy.java

public class LazyExpireStrategy<K, V> implements ExpireStrategy<K, V> {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 清空过期Key-Value
     *
     * @param localCache 本地缓存底层使用的存储结构
     * @param key 缓存的键
     * @return 过期的值
     */
    @Override
    public V removeExpireKey(LinkedHashMap<K, CacheNode<K, V>> localCache, K key) {
        CacheNode<K, V> baseCacheValue = localCache.get(key);
        //值不存在
        if (baseCacheValue == null) {
            logger.info("key:{}对应的value不存在", key);
            return null;
        } else {
            //值存在并且未过期
            if (baseCacheValue.getExpireTime() - System.currentTimeMillis() > 0) {
                return baseCacheValue.getValue();
            }
        }

        logger.info("key:{}已过期,进行懒删除key操作", key);
        localCache.remove(key);
        return null;
    }
}
复制代码

Description:

  • Determines whether the key exists, the absence of return nullvalue
  • If the key exists, determine whether the expired, but the return period, expired deletion and return nullvalue

Implement caching method of operating

  • delete
    public synchronized V removeKey(K key) {
      CacheNode<K, V> cacheNode = localCache.remove(key);
      return cacheNode != null ? cacheNode.getValue() : null;
    }
    复制代码
  • Seek
    public synchronized V getValue(K key) {
      return lazyExpireStrategy.removeExpireKey(localCache, key);
    }
    复制代码
    When looking for will go lazy deletion policy
  • Deposit
    deposit value does not fail:
    public synchronized V putValue(K key, V value) {
        CacheNode<K, V> cacheNode = new CacheNode<>();
        cacheNode.setKey(key);
        cacheNode.setValue(value);
        localCache.put(key, cacheNode);
        // 返回添加的值
        return value;
    }
    复制代码
    Stored value failure:
    public synchronized V putValue(K key, V value, long expireTime) {
      CacheNode<K, V> cacheNode = new CacheNode<>();
      cacheNode.setKey(key);
      cacheNode.setValue(value);
      cacheNode.setGmtCreate(System.currentTimeMillis() + expireTime);
      localCache.put(key, cacheNode);
      // 返回添加的值
      return value;
    }
    复制代码
  • Set the cache expiration time
    public synchronized void setExpireKey(K key, long expireTime) {
      if (localCache.get(key) != null) {
        localCache.get(key).setExpireTime(System.currentTimeMillis() + expireTime);
      }
    }
    复制代码
  • Gets the cache size
    public synchronized int getLocalCacheSize() {
            return localCache.size();
    }
    复制代码

All methods in order to ensure the security thread are used synchronizekeyword [thread-safe, how sweet will synchronizenot think of any other better way of locking considered read-write locks, but does not work ,,,]

Use gestures

  • Creating objects LocalCache
    • A posture
      LocalCache<Integer, Integer> localCache = new LocalCache<>(4, null);
      复制代码
      The first parameter size of the cache, allowing the storage of the number of cache second parameter periodically delete an object, if it is null, use the default execution cycle [regularly delete objects, execution time, the number of executions are the default values]
    • Pose two
      RegularExpireStrategy<Integer, Integer> expireStrategy = new RegularExpireStrategy<>();
      expireStrategy.setExecuteRate(1); //每隔1分钟执行一次
      LocalCache<Integer, Integer> localCache = new LocalCache<>(4, expireStrategy);
      复制代码
      Incoming periodically delete custom objects
  • Cached
    for (int i = 0; i < 16; i++) {
      localCache.putValue(i, i);
    }
    复制代码
  • Cached and set the expiration time
     localCache.putValue(i, i,1000);
    复制代码
  • Values ​​are read from the cache
    localCache.getValue(i)
    复制代码
  • Has been set up in the data cache expiration
    localCache.setExpireKey(i, 1000)
    复制代码
  • Gets size of the cache
    localCache.getLocalCacheSize()
    复制代码
  • Delete Cache
    localCache.removeKey(i)
    复制代码

Write the purpose of learning a local cache, the practical application is recommended to use Googlethe Guava Cache, if you are confident enough of my code, of course, mention WelcomeBug

Optimization points

  • Use ConcurrentHashMapplus + doubly linked list
  • Cache invalidation, diversity timer task timing, the cache miss currently using the default unit is ms, the default timer task in minutes, by adding in the process TimeUnitparameters selected more time diversity
  • Concurrent support for the poor, to achieve synchronous [what sweet vegetables too! ! ! ]

ALL

  • Upload PW, dependent manner provided for use in other projects, the way to learn how to upload PW


Last Attachment: Project complete code , welcomed the fork , Star
if wrong, please correct me how sweet exchange [really too dishes! ! ! ]

Guess you like

Origin juejin.im/post/5db98f47e51d452a18445ed6