美文网首页架构设计与重构互联网架构
动手写框架-轻量级开源缓存MkCache

动手写框架-轻量级开源缓存MkCache

作者: monkey01 | 来源:发表于2018-01-25 16:13 被阅读73次

    前言

    之前在很多项目中都用到了缓存,常用的有memcache、redis、ehcache等,这些都是目前主流的缓存中间件,今天我给大家介绍下自己写的一个轻量级的缓存框架MkCache,这个轻量级框架能够满足一般项目的使用,目前的0.1版本还比较简单只能实现线程安全的内存缓存,支持标准hash缓存、基于LRU算法的hash缓存、弱引用的hash缓存,后续会增加分布式集群支持,数据库一致性支持等功能。下面就简单介绍下如何使用和MkCache的架构和关键类。

    github地址:https://github.com/feiweiwei/MkCache

    如何使用MkCache

    MkCache的使用非常简单,可以直接将源码放到项目的目录中,也可以引用jar包,后续会发布到OSS中这样直接在pom里就可以引用了。

    普通的java对象缓存

    MkCache<String, String> cache = new MkCache<String, String>(new BasicDataStore<String, String>());
    String key = "Hello";
    cache.put(key, "World!");
    Assert.assertEquals("World!", cache.get(key));
    

    基于LRU算法的对象缓存

    MkCache<String, User> cache = new MkCache<String, User>(new LRUDataStore<String, User>(2));
    String key = "fei";
    User user = new User();
    user.setName("fei");
    
    String key1 = "du";
    User user1 = new User();
    user1.setName("du");
    
    String key2 = "wang";
    User user2 = new User();
    user2.setName("wang");
    
    cache.put(key, user);
    cache.put(key1, user1);
    cache.get(key);
    cache.put(key2, user2);
    
    if (cache.get(key) != null) {
      Assert.assertEquals("fei", cache.get(key).getName());     
    }
    if (cache.get(key1) != null) {
      Assert.assertEquals("du", cache.get(key1).getName());     
    }
    if (cache.get(key2) != null) {
      Assert.assertEquals("wang", cache.get(key2).getName());       
    }
    }
    

    使用起来是不是很简单。

    MkCache架构

    Screenshot 2018-01-25 15.12.00

    整个MkCache缓存框架的核心类主要有这么几个,每一层都是面向接口编程这样可以方便的进行后续扩展。

    类名 功能
    ValueHolder 缓存value类型接口,所有缓存中Value类型都是实现该接口
    BasicValueHolder 缓存基础Vaule类,基础范型类,常用的缓存对象都可以使用该类
    WeakValueHolder 弱引用value数据类型,对于在缓存中需要弱引用的类型可以使用该类
    DataStore 缓存数据存储接口,所有缓存数据类型都实现该接口
    BasicDataStore 缓存数据存储基础类,基本缓存存储类型没有特殊算法,没有特殊要求的缓存都可以使用该类
    WeakValueDataStore 弱引用数据类型缓存数据类型
    LRUDataStore 缓存数据存储LRU类,基本缓存存储类型缓存所有操作采用LRU算法
    LRUEntry LRU链表节点,里面定义了链表节点的前后节点的数据结构
    MkCache MkCache缓存核心调用类,都是缓存的接口都是通过这个接口对外暴露

    MkCache关键类实现

    MkCache类

    public class MkCache<K, V> {
        private final DataStore<K, V> store;
    
        public MkCache(final DataStore<K, V> dataStore) {
            store = dataStore;
        }
    
        public V get(final K key) {
            try {
                ValueHolder<V> value = store.get(key);
                if (null == value) {
                    return null;
                }
                return value.value();
            } catch (MkCacheException e) {
                System.out.println(e.getStackTrace().toString());
                return null;
            }
        }
    
        public void put(final K key, final V value) {
            try {
                store.put(key, value);
            } catch (MkCacheException e) {
                System.out.println(e.getStackTrace().toString());
    
            }
        }
    
        public V remove(K key) {
            try {
                ValueHolder<V> value = store.remove(key);
                return value.value();
            } catch (MkCacheException e) {
                System.out.println(e.getStackTrace().toString());
    
                return null;
            }
        }
    
        public void clear() {
            try {
                store.clear();
            } catch (MkCacheException e) {
                System.out.println(e.getStackTrace().toString());
    
            }
        }
    }
    

    BasicDataStore类

    public class BasicDataStore<K, V> implements DataStore<K, V> {
    
        ConcurrentHashMap<K, ValueHolder<V>> map = new ConcurrentHashMap<K, ValueHolder<V>>();
    
        @Override
        public ValueHolder<V> get(K key) throws MkCacheException {
            return map.get(key);
        }
    
        @Override
        public PutStatus put(K key, V value) throws MkCacheException {
            ValueHolder<V> v = new BasicValueHolder<V>(value);
            map.put(key, v);
            return PutStatus.PUT;
        }
    
        @Override
        public ValueHolder<V> remove(K key) throws MkCacheException {
            return map.remove(key);
        }
    
        @Override
        public void clear() throws MkCacheException {
            map.clear();
        }
    
    }
    

    LRUDataStore类是相对复杂的一个基于LRU算法实现的数据缓存类型,类里维护了一个LRU链表,所有的get、put操作都基于LRU算法实现。

    public class LRUDataStore<K, V> implements DataStore<K, V> {
    
        public LRUDataStore(int maxSize) {
            super();
            this.maxSize = maxSize;
        }
    
        /**
         * 构造函数定义LRU缓存数据存储的链表最大长度
         */
        private final int maxSize;
    
        /**
         * 定义一个ConcurrentHashMap,用于存储LRUEntry,存储数据结构为HashMap,逻辑数据结构为一个链表
         */
        ConcurrentHashMap<K, LRUEntry<K, ValueHolder<?>>> map = new ConcurrentHashMap<K, LRUEntry<K, ValueHolder<?>>>();
    
        /**
         * LRU链表的首节点first
         */
        private LRUEntry<K, ValueHolder<?>> first;
        /**
         * LRU链表的末节点last
         */
        private LRUEntry<K, ValueHolder<?>> last;
    
        @SuppressWarnings("unchecked")
        @Override
        public ValueHolder<V> get(K key) throws MkCacheException {
            LRUEntry<K, ValueHolder<?>> entry = (LRUEntry<K, ValueHolder<?>>) getEntry(key);
            if (entry == null) {
                return null;
            }
            //获取到相应LRU节点后,将该节点移动到链表头
            moveToFirst(entry);
            return (ValueHolder<V>) entry.getValue();
    
        }
    
        @Override
        public PutStatus put(K key, V value) throws MkCacheException {
            LRUEntry<K, ValueHolder<?>> entry = (LRUEntry<K, ValueHolder<?>>) getEntry(key);
            PutStatus status = PutStatus.DROP;
            //如果LRU缓存中没有该key,则加入链表
            if (entry == null) {
                //如果LRU链表长度大于最大值则删除掉最后一个节点
                if (map.size() >= maxSize) {
                    //map删除最后一个节点
                    map.remove(last.getKey());
                    //LRU链表末节点前移一个节点
                    removeLast();
                }
                entry = new LRUEntry<K, ValueHolder<?>>(key, new BasicValueHolder<V>(value));
                status = PutStatus.PUT;
            } else {
                entry.setValue(new BasicValueHolder<V>(value));
                status = PutStatus.UPDATE;
            }
            //根据LRU算法将该节点移动到首节点
            moveToFirst(entry);
            map.put(key, entry);
            return status;
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public ValueHolder<V> remove(K key) throws MkCacheException {
            LRUEntry<K, ValueHolder<?>> entry = getEntry(key);
            //如果能查到该entry则进行删除操作
            if (entry != null) {
                if (entry.getPre() != null) {
                    entry.getPre().setNext(entry.getNext());
                }
                if (entry.getNext() != null) {
                    entry.getNext().setPre(entry.getPre());
                }
                if (entry == first) {
                    first = entry.getNext();
                }
                if (entry == last) {
                    last = entry.getPre();
                }
            }
            LRUEntry<K, ValueHolder<?>> oldValue = map.remove(key);
            return (ValueHolder<V>) oldValue.getValue();
        }
    
        @Override
        public void clear() throws MkCacheException {
            this.map.clear();
            this.first = this.last = null;
        }
    
        private void moveToFirst(LRUEntry<K, ValueHolder<?>> entry) {
            if (entry == first) {
                return;
            }
            if (entry.getPre() != null) {
                entry.getPre().setNext(entry.getNext());
            }
            if (entry.getNext() != null) {
                entry.getNext().setPre(entry.getPre());
            }
            if (entry == last) {
                last = last.getPre();
            }
            if (first == null || last == null) {
                first = last = entry;
                return;
            }
    
            entry.setNext(first);
            first.setPre(entry);
            first = entry;
            entry.setPre(null);
        }
    
        private void removeLast() {
            if (last != null) {
                last = last.getPre();
                if (last == null)
                    first = null;
                else
                    last.next = null;
            }
        }
    
        private LRUEntry<K, ValueHolder<?>> getEntry(K key) {
            return map.get(key);
        }
    
    }
    

    LRUEntry类,LRU链表节点类,其中定义了一个单向链表的节点数据结构。

    public class LRUEntry<K, V extends ValueHolder<?>> implements Entry<K, ValueHolder<?>> {
    
        final K key; // non-null
        ValueHolder<?> v; // non-null
    
        LRUEntry<K, ValueHolder<?>> pre;
        LRUEntry<K, ValueHolder<?>> next;
    
        public LRUEntry<K, ValueHolder<?>> getPre() {
            return pre;
        }
    
        public void setPre(LRUEntry<K, ValueHolder<?>> pre) {
            this.pre = pre;
        }
    
        public LRUEntry<K, ValueHolder<?>> getNext() {
            return next;
        }
    
        public void setNext(LRUEntry<K, ValueHolder<?>> next) {
            this.next = next;
        }
    
        public LRUEntry(K key, V value) {
            super();
    
            this.key = key;
            this.v = value;
        }
    
        @Override
        public K getKey() {
            return key;
        }
    
        @Override
        public ValueHolder<?> getValue() {
            return this.v;
        }
    
        @Override
        public ValueHolder<?> setValue(ValueHolder<?> value) {
            ValueHolder<?> oldValue = this.v;
            this.v = value;
            return oldValue;
        }
    
    }
    

    MkCache规划

    MkCache目前0.1版本是比较轻量级的一个版本,该版本还不支持集群模式,只支持基本的分布式线性部署,而且也没有自动失效机制,后续会增加分布式集群支持,数据库一致性支持等功能,还要解决缓存穿透和缓存同时失效等缓存常见问题。

    相关文章

      网友评论

        本文标题:动手写框架-轻量级开源缓存MkCache

        本文链接:https://www.haomeiwen.com/subject/joivaxtx.html