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

前言

之前在很多项目中都用到了缓存,常用的有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版本是比较轻量级的一个版本,该版本还不支持集群模式,只支持基本的分布式线性部署,而且也没有自动失效机制,后续会增加分布式集群支持,数据库一致性支持等功能,还要解决缓存穿透和缓存同时失效等缓存常见问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260