作者:ksfzhaohuihttp://my.oschina.net/OutOfMemory/blog/3133013

前言

最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级缓存和二级缓存;一级缓存相对来说比较简单,功能比较齐全的是二级缓存,基本上满足了一个缓存该有的功能;当然如果拿来和专门的缓存框架如ehcache来对比可能稍有差距;本文我们将来整理一下实现一个本地缓存都应该需要考虑哪些东西。

考虑点

考虑点主要在数据用何种方式存储,能存储多少数据,多余的数据如何处理等几个点,下面我们来详细的介绍每个考虑点,以及该如何去实现;

1.数据结构

首要考虑的就是数据该如何存储,用什么数据结构存储,最简单的就直接用Map来存储数据;或者复杂的如redis一样提供了多种数据类型哈希,列表,集合,有序集合等,底层使用了双端链表,压缩列表,集合,跳跃表等数据结构;

2.对象上限

因为是本地缓存,内存有上限,所以一般都会指定缓存对象的数量比如1024,当达到某个上限后需要有某种策略去删除多余的数据;

3.清除策略

上面说到当达到对象上限之后需要有清除策略,常见的比如有LRU(最近最少使用)、FIFO(先进先出)、LFU(最近最不常用)、SOFT(软引用)、WEAK(弱引用)等策略;

4.过期时间

除了使用清除策略,一般本地缓存也会有一个过期时间设置,比如redis可以给每个key设置一个过期时间,这样当达到过期时间之后直接删除,采用清除策略+过期时间双重保证;

5.线程安全

像redis是直接使用单线程处理,所以就不存在线程安全问题;而我们现在提供的本地缓存往往是可以多个线程同时访问的,所以线程安全是不容忽视的问题;并且线程安全问题是不应该抛给使用者去保证;

6.简明的接口

提供一个傻瓜式的对外接口是很有必要的,对使用者来说使用此缓存不是一种负担而是一种享受;提供常用的get,put,remove,clear,getSize方法即可;

7.是否持久化

这个其实不是必须的,是否需要将缓存数据持久化看需求;本地缓存如ehcache是支持持久化的,而guava是没有持久化功能的;分布式缓存如redis是有持久化功能的,memcached是没有持久化功能的;

8.阻塞机制

在看Mybatis源码的时候,二级缓存提供了一个blocking标识,表示当在缓存中找不到元素时,它设置对缓存键的锁定;这样其他线程将等待此元素被填充,而不是命中数据库;其实我们使用缓存的目的就是因为被缓存的数据生成比较费时,比如调用对外的接口,查询数据库,计算量很大的结果等等;这时候如果多个线程同时调用get方法获取的结果都为null,每个线程都去执行一遍费时的计算,其实也是对资源的浪费;最好的办法是只有一个线程去执行,其他线程等待,计算一次就够了;但是此功能基本上都交给使用者来处理,很少有本地缓存有这种功能;

如何实现

以上大致介绍了实现一个本地缓存我们都有哪些需要考虑的地方,当然可能还有其他没有考虑到的点;下面继续看看关于每个点都应该如何去实现,重点介绍一下思路;

1.数据结构

本地缓存最常见的是直接使用Map来存储,比如guava使用ConcurrentHashMap,ehcache也是用了ConcurrentHashMap,Mybatis二级缓存使用HashMap来存储:

Map<Object, Object> cache = new ConcurrentHashMap<Object, Object>()

Mybatis使用HashMap本身是非线程安全的,所以可以看到其内部使用了一个SynchronizedCache用来包装,保证线程的安全性;

当然除了使用Map来存储,可能还使用其他数据结构来存储,比如redis使用了双端链表,压缩列表,整数集合,跳跃表和字典;当然这主要是因为redis对外提供的接口很丰富除了哈希还有列表,集合,有序集合等功能;

2.对象上限

本地缓存常见的一个属性,一般缓存都会有一个默认值比如1024,在用户没有指定的情况下默认指定;当缓存的数据达到指定最大值时,需要有相关策略从缓存中清除多余的数据这就涉及到下面要介绍的清除策略;

3.清除策略

配合对象上限之后使用,场景的清除策略如:LRU(最近最少使用)、FIFO(先进先出)、LFU(最近最不常用)、SOFT(软引用)、WEAK(弱引用);

  • LRU:Least Recently Used的缩写最近最少使用,移除最长时间不被使用的对象;常见的使用LinkedHashMap来实现,也是很多本地缓存默认使用的策略;
  • FIFO:先进先出,按对象进入缓存的顺序来移除它们;常见使用队列Queue来实现;
  • LFU:Least Frequently Used的缩写大概也是最近最少使用的意思,和LRU有点像;区别点在LRU的淘汰规则是基于访问时间,而LFU是基于访问次数的;可以通过HashMap并且记录访问次数来实现;
  • SOFT:软引用基于垃圾回收器状态和软引用规则移除对象;常见使用SoftReference来实现;
  • WEAK:弱引用更积极地基于垃圾收集器状态和弱引用规则移除对象;常见使用WeakReference来实现;

4.过期时间

设置过期时间,让缓存数据在指定时间过后自动删除;常见的过期数据删除策略有两种方式:被动删除和主动删除;

被动删除:每次进行get/put操作的时候都会检查一下当前key是否已经过期,如果过期则删除,类似如下代码:

if (System.currentTimeMillis() - lastClear > clearInterval) {clear();
}

主动删除:专门有一个job在后台定期去检查数据是否过期,如果过期则删除,这其实可以有效的处理冷数据;

5.线程安全

尽量用线程安全的类去存储数据,比如使用ConcurrentHashMap代替HashMap;或者提供相应的同步处理类,比如Mybatis提供了SynchronizedCache:

 public synchronized void putObject(Object key, Object object) {...省略...}@Overridepublic synchronized Object getObject(Object key) {...省略...}

6.简明的接口

提供常用的get,put,remove,clear,getSize方法即可,比如Mybatis的Cache接口:

public interface Cache {String getId();void putObject(Object key, Object value);Object getObject(Object key);Object removeObject(Object key);void clear();int getSize();ReadWriteLock getReadWriteLock();
}

再来看看guava提供的Cache接口,相对来说也是比较简洁的:

public interface Cache<K, V> {V getIfPresent(@CompatibleWith("K") Object key);V get(K key, Callable<? extends V> loader) throws ExecutionException;ImmutableMap<K, V> getAllPresent(Iterable<?> keys);void put(K key, V value);void putAll(Map<? extends K, ? extends V> m);void invalidate(@CompatibleWith("K") Object key);void invalidateAll(Iterable<?> keys);void invalidateAll();long size();CacheStats stats();ConcurrentMap<K, V> asMap();void cleanUp();
}

7.是否持久化

持久化的好处是重启之后可以再次加载文件中的数据,这样就起到类似热加载的功效;比如ehcache提供了是否持久化磁盘缓存的功能,将缓存数据存放在一个.data文件中;

diskPersistent="false" //是否持久化磁盘缓存

redis更是将持久化功能发挥到极致,慢慢的有点像数据库了;提供了AOF和RDB两种持久化方式;当然很多情况下可以配合使用两种方式;

8.阻塞机制

除了在Mybatis中看到了BlockingCache来实现此功能,之前在看<>的时候其中有实现一个很完美的缓存,大致代码如下:

public class Memoizerl<A, V> implements Computable<A, V> {private final Map<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();private final Computable<A, V> c;public Memoizerl(Computable<A, V> c) {this.c = c;}@Overridepublic V compute(A arg) throws InterruptedException, ExecutionException {while (true) {Future<V> f = cache.get(arg);if (f == null) {Callable<V> eval = new Callable<V>() {@Overridepublic V call() throws Exception {return c.compute(arg);}};FutureTask<V> ft = new FutureTask<V>(eval);f = cache.putIfAbsent(arg, ft);if (f == null) {f = ft;ft.run();}try {return f.get();} catch (CancellationException e) {cache.remove(arg, f);}}}}
}

compute是一个计算很费时的方法,所以这里把计算的结果缓存起来,但是有个问题就是如果两个线程同时进入此方法中怎么保证只计算一次,这里最核心的地方在于使用了ConcurrentHashMap的putIfAbsent方法,同时只会写入一个FutureTask;

总结

本文大致介绍了要设计一个本地缓存都需要考虑哪些点:数据结构,对象上限,清除策略,过期时间,线程安全,阻塞机制,实用的接口,是否持久化;当然肯定有其他考虑点,欢迎补充。

推荐阅读(点击即可跳转阅读)

1. SpringBoot内容聚合

2.

面试题内容聚合​mp.weixin.qq.com

3. 设计模式内容聚合

4. Mybatis内容聚合

5. 多线程内容聚合

ehcache 清除缓存_如何设计一个本地缓存相关推荐

  1. 为什么用redis做缓存而不是mybatis自带的缓存_如何用Java设计一个本地缓存,涨姿势了...

    最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级缓存和二级缓存:一级缓存相对来说比较简单,功能比较齐全的是二级缓存,基本上满足了一个缓存该有的功能. 当然如果拿来和专门的缓存 ...

  2. 如何设计一个本地缓存

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:http://i7q.cn/4xPYgB 前言 最近在看My ...

  3. Java技术分享:如何设计一个本地缓存?

    前言 最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级缓存和二级缓存:一级缓存相对来说比较简单,功能比较齐全的是二级缓存,基本上满足了一个缓存该有的功能:当然如果拿来和专门的 ...

  4. fifo页面置换算法设计思路_千万级并发!如何设计一个多级缓存系统?

    什么是一个多级缓存系统?它有什么用?我们又如何设计一个多级缓存系统? 图片来自 Pexels 所谓多级缓存系统,就是指在一个系统的不同的架构层级进行数据缓存,以提升访问效率. 我们都知道,一个缓存系统 ...

  5. java本地缓存_java缓存——(五)LocalCache本地缓存分享

    LocalCache本地缓存分享 前言 一.本地缓存应用场景 二.java本地缓存标准 三.java开源缓存框架 四.LocalCache实现 结束语 前言 本次分享探讨java平台的本地缓存,是指占 ...

  6. 本地缓存:为什么要用本地缓存?用它会有什么问题?

    背景 在高性能的服务架构设计中,缓存是一个不可或缺的环节.在实际的项目中,我们通常会将一些热点数据存储到Redis或Memcached 这类缓存中间件中,只有当缓存的访问没有命中时再查询数据库.在提升 ...

  7. 京东java多级缓存_京东技术:多级缓存设计详解 | 给数据库减负

    来这里找志同道合的小伙伴! 作 者 简 介 王梓晨 自古兵家多谋,<谋攻篇>,"故上兵伐谋,其次伐交,其次伐兵,其下攻城.攻城之法,为不得已",可见攻城之计有很多种,而 ...

  8. 千万级并发!如何设计一个多级缓存系统?

    作者:不清不慎,目前在杭州蘑菇街公司任职,Java大数据开发工程师一枚,热爱研究开源技术! 架构师社区合伙人! 首先我们需要明白,什么是一个多级缓存系统,它有什么用.所谓多级缓存系统,就是指在一个系统 ...

  9. mysql每秒支持多少并发_如何设计一个高并发系统?

    面试题 如何设计一个高并发系统? 面试官心理分析 说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了.为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先. 如果你确 ...

最新文章

  1. CV之后,纯MLP架构又来搞NLP了,性能媲美预训练大模型
  2. 几种Linux包管理系统的命令对照
  3. powershell命令大全
  4. The 'microsoft.jet.oledb.4.0' provider is not registered on the local machin
  5. 游戏开发-从零开始 002
  6. 震撼世界的基建狂魔,中国制造的超级工程到底有多牛?
  7. 20个常用的Python小技巧
  8. Java基础(三)选择和循环结构
  9. C++ std::remove/std::remove_if/erase用法探讨
  10. 如何使用p6spy来监控sql
  11. 通过命令行获取计算机参数,Win32命令行参数的传到和获取
  12. Windows下android模拟器环境搭建
  13. 递归算法—输入字母逆序输出汉诺塔递归算法
  14. 用Markdown做日记和任务跟踪的DailyNotes
  15. blender做MMD心得(三)
  16. Play on Words
  17. 全智通A+常见问题汇总解答—A+配件仓库—维修领料—编辑领料单:最后一个仓库无法显示
  18. python随机生成licence plate numer
  19. 如何破解Aspose.word带水印问题
  20. java.lang.NullPointterException:

热门文章

  1. android复位机器人图片,安卓(Android)新图标!小绿机器人换脸啦
  2. python怎么赋值int_int对象不支持项赋值
  3. oracle 数据文件recover,Oracle数据库意外删除数据文件的恢复(转载)
  4. 编译时,输出信息重定向到文件
  5. 谈谈如何使用Netty开发实现高性能的RPC服务器
  6. 20155220吴思其 实验2 Windows口令破解
  7. 安装mysql数据库要注意的
  8. spark内核揭秘-09-RDD的count操作 触发Job全生命周期-01
  9. 大型ERP等数据库系统常见几种设计------(转)
  10. MySQL修改字符集步骤(字段插入中文提示错误时解决办法)