最近在看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 cache = new ConcurrentHashMap()

Mybatis使用HashMap本身是非线程安全的,所以可以看到起内部使用了一个SynchronizedCache用来包装,保证线程的安全性。面试必问-几种线程安全的Map解析,这篇推荐大家看下。

当然除了使用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) { ...省略...}

提供常用的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 { V getIfPresent(@CompatibleWith("K") Object key); V get(K key, Callable extends V> loader) throws ExecutionException; ImmutableMap 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 asMap(); void cleanUp();}

7.是否持久化

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

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

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

8.阻塞机制

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

public class Memoizerl implements Computable { private final Map> cache = new ConcurrentHashMap>(); private final Computable c; public Memoizerl(Computable c) { this.c = c; } @Override public V compute(A arg) throws InterruptedException, ExecutionException { while (true) { Future f = cache.get(arg); if (f == null) { Callable eval = new Callable() { @Override public V call() throws Exception { return c.compute(arg); } }; FutureTask ft = new FutureTask(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;

总结

本文大致介绍了要设计一个本地缓存都需要考虑哪些点:数据结构,对象上限,清除策略,过期时间,线程安全,阻塞机制,实用的接口,是否持久化;当然更多Java学习资料,可以关注“武汉千锋”微信公众号!

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

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

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

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

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

  3. ehcache 清除缓存_如何设计一个本地缓存

    作者:ksfzhaohuihttp://my.oschina.net/OutOfMemory/blog/3133013 前言 最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级 ...

  4. 怎么用java做日历_如何用Java制作一个简易日历

    简易日历制作 记录一下Java实现的一个日历小程序,效果图如下: 实现以上的效果,我们需要用到两个类:SimpleDateFormat和Calendar. 首先看看这两个类的用法: 类 SimpleD ...

  5. 手机java做贪吃蛇_如何用Java写一个贪吃蛇游戏

    这是一位拓胜学员用Java写贪吃蛇游戏的心得:今天课程设计终于结束了自己学java没以前学C+那么用功了觉得我学习在哪里都是个开口向上的抛物线,现在应该在右半边吧,好了进入正题. 写java贪吃蛇也是 ...

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

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

  7. Java简单实现本地缓存

    Java简单实现本地缓存 注意:此代码不适合集群部署环境 import java.util.Map; import java.util.concurrent.ConcurrentHashMap;/** ...

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

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

  9. 手写实现一个本地缓存

    非并发的条件使用hashmap,并发的条件使用ConcurrentHashMap 考虑过期和缓存淘汰 过期策略常见 定时删除 设置键值的过期时间,创建一个定时时间.当到达过期时间后,事件处理器会执行删 ...

最新文章

  1. LeetCode 72. Edit Distance--动态规划--Levenshtein Distance Algorithm--Java,Python解法
  2. python【蓝桥杯vip练习题库】BASIC-20 数的读法
  3. 评价指标:目标检测的评价指标 - mAP
  4. 写一个简单的 django_post demo
  5. 06-06 Jenkins 邮件报警机制
  6. linux 下Eclipse for C/C++的不常见设置
  7. 使用Python内置浏览器缓存cookies并做更新
  8. 使用autogen工具生成Makefile遇到问题解决思路
  9. python能做什么excel-python可以用来做excel吗
  10. flex学习笔记 富文本编辑(一)-RichEditableText
  11. crossover卸载 linux,Ubuntu中卸载CrossOver困难?那是你没学会这个命令
  12. 数据库设计原则、表字段命名规则、索引调优建立规则
  13. 为什么要选择crm私有化布署?
  14. 算法在身边——学习算法从妈妈的菜谱开始
  15. 浏览器控制台Network面板简述
  16. 开放阿里云指定端口及设置本地代理访问自己的阿里云服务器
  17. PHP利用ImageMagick实现PDF、PPT转图片
  18. 微信小程序--行星轨迹
  19. 图像融合论文及代码网址整理总结(1)——多聚焦图像融合
  20. 示波器 DC50Ω与DC1M

热门文章

  1. Mysql 零距离-入门(一)
  2. python include的功能_在Python的Config中增加Include功能
  3. 如何进入embl的ebi网站fasta3服务器,The EMBL-EBI bioinformatics web and programmatic tools framework...
  4. 没有bug队——加贝——Python 45,46
  5. 使用MATLAB GUI创建图形用户界面GUI
  6. php中update()函数,update_option()函数
  7. java中文分词算法_Java实现逆向最大匹配中文分词算法
  8. Win10调试ssd_tensorflow的目标检测
  9. 【Python】pyCryptodome模块实现AES加密、解密
  10. 产品经理:论项目计划有多重要?——软件项目管理