点击上方“方志朋”,选择“设为星标”

  • 回复”666“获取新整理的面试资料

  • 来源:http://i7q.cn/4xPYgB

前言

最近在看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) {
...省略...
}@Override
public 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来实现此功能,之前在看<<java并发编程实战>>的时候其中有实现一个很完美的缓存,大致代码如下:

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;

总结

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

热门内容:
解锁新姿势:探讨复杂的 if-else 语句“优雅处理”的思路
HttpClient连接池设置引发的一次雪崩
SpringBoot 整合 Shiro 实现动态权限加载更新+ Session 共享 + 单点登录老弟,你连HTTPS 原理都不懂,还给我讲“中间人攻击”,逗我吗...Mybatis:颠覆你心中对事务的理解为什么强烈推荐 Java 程序员使用 Google Guava 编程!IntelliJ IDEA 快捷键终极大全,速度收藏!「Jenkins+Git+Maven+Shell+Tomcat持续集成」经典教程  最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡

如何设计一个本地缓存相关推荐

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

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

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

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

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

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

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

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

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

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

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

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

  7. 教你设计一个超牛逼的本地缓存!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:ksfzhaohui juejin.im/post/5dd9 ...

  8. 如何设计一个牛逼的本地缓存

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:ksfzhaohui juejin.im/post/5dd9 ...

  9. 如何设计一个牛逼的本地缓存!

    来源:ksfzhaohui | http://dwz.win/Ws4 最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级缓存和二级缓存:一级缓存相对来说比较简单,功能比较齐全的 ...

最新文章

  1. 04JavaScript中的运算符
  2. 分享mac磁盘清理的方法
  3. 构造 HDOJ 5400 Arithmetic Sequence
  4. Spring MVC POST中文乱码解决方案
  5. Elasticsearch-搜索并获取数据
  6. SAP OData Service group - get entity set
  7. AtCoder Beginner Contest 194 F - Digits Paradise in Hexadecimal 数位dp
  8. .Net程序猿玩转Android开发---(11)页面跳转
  9. Mysql replace 与 insert on duplicate效率分析
  10. gdb 查看是否 栈溢出_64位Linux栈溢出教程
  11. Uncode-Schedule首页、文档和下载 - 分布式任务调度组件 - 开源中国社区
  12. .pth文件转.weight文件For YOLO
  13. AArch64架构内存布局及线性地址转换
  14. excel(2015)表格如何在滑动时固定标题栏
  15. Exp5 MSF基础应用 20164323段钊阳
  16. 用C语言编写函数multiple求倍数、用C语言编写函数isEven判断奇数和偶数
  17. 【基础】ARM芯片上电取第一条指令流程
  18. 语音识别技术突飞猛进,语音识别公司都有哪些?
  19. 支付平台--清结算流程详解及对账详解
  20. python算法工程师-从材料硕士到算法工程师的转行之路,有三不建议

热门文章

  1. vue-cli脚手架(框架)
  2. 从云端到边缘 AI推动FPGA应用拓展
  3. 多IP绑定与多网卡绑定
  4. 我的路子 - 发现游戏为模型的软件架构方式
  5. JS中根据某个值进行大小排序
  6. Android Java使用JavaMail API发送和接收邮件的代码示例
  7. POJ 1556 The Doors(计算几何+最短路)
  8. 大宗商品(Bulk Stock)交易
  9. IOS问题汇总:2012-12-18 UIAlertView+UIActionSheet
  10. C++ 常用函数方法