如何设计一个本地缓存

场景应用:如何设计一个本地缓存?(附加代码实现) 我们使用缓存的目的就是因为被缓存的数据生成比较费时,比如调用对外的接口,查询数据库,计算量很大的结果等等。最简单的就直接用Map来存储数据,或者复杂的如redis一样提供了多种数据类型哈希,列表,集合,有序集合等,底层使用了双端链表,压缩列表,集合,跳跃表等数据结构。分布式缓存如redis是有持久化功能的,memcached是没有持久化功能的。提供一个傻瓜式的对外接口是很有必要的,对使用者来说使用此缓存不是一种负担而是一种享受,提供常用的get,put,remove,clear,getSize等方法即可。 阅读详情

原文地址 https://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 来存储:


   
  1. 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 是否已经过期,如果过期则删除,类似如下代码:


   
  1. if(System.currentTimeMillis() - lastClear > clearInterval) {

  2. clear();

  3. }

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

5. 线程安全

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


   
  1. publicsynchronizedvoid putObject(Object key, Objectobject) {

  2. ...省略...

  3. }

  4. @Override

  5. publicsynchronizedObject getObject(Object key) {

  6. ...省略...

  7. }

6. 简明的接口

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


   
  1. publicinterfaceCache{

    1. String getId();

    2. void putObject(Object key, Object value);

    3. Object getObject(Object key);

    4. Object removeObject(Object key);

    5. void clear();

    6. int getSize();

    7. ReadWriteLock getReadWriteLock();

  2. }

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


   
  1. publicinterfaceCache<K, V> {

  2. V getIfPresent(@CompatibleWith("K") Object key);

  3. V get(K key, Callable<? extends V> loader) throwsExecutionException;

    1. ImmutableMap<K, V> getAllPresent(Iterable<?> keys);

  4. void put(K key, V value);

  5. void putAll(Map<? extends K, ? extends V> m);

  6. void invalidate(@CompatibleWith("K") Object key);

  7. void invalidateAll(Iterable<?> keys);

  8. void invalidateAll();

  9. long size();

  10. CacheStats stats();

  11. ConcurrentMap<K, V> asMap();

  12. void cleanUp();

  13. }

7. 是否持久化

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


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

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

8. 阻塞机制

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


   
  1. public class Memoizerl<A, V> implements Computable<A, V> {

  2. private final Map<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();

  3. private final Computable<A, V> c;

  4. public Memoizerl(Computable<A, V> c) {

  5. this.c = c;

  6. }

  7. @Override

  8. public V compute(A arg) throws InterruptedException, ExecutionException {

  9. while (true) {

  10. Future<V> f = cache.get(arg);

  11. if (f == null) {

  12. Callable<V> eval = new Callable<V>() {

  13. @Override

  14. public V call() throws Exception {

  15. return c.compute(arg);

  16. }

  17. };

  18. FutureTask<V> ft = new FutureTask<V>(eval);

  19. f = cache.putIfAbsent(arg, ft);

  20. if (f == null) {

  21. f = ft;

  22. ft.run();

  23. }

  24. try {

  25. return f.get();

  26. } catch (CancellationException e) {

  27. cache.remove(arg, f);

  28. }

  29. }

  30. }

  31. }

  32. }

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

总结

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

(完)

(java思维导图)

长按关注,每天java一下,成就架构师

我就知道你在看!

Java本地缓存的实现代码 本篇文章主要介绍了Java本地缓存的实现代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 立即下载

相关推荐

R 分析裂区试验设计

裂区试验有主区部分和副区部分, 因而有主区部分误差(误差 a,简记为Ea )和副区部分误差(误差b,简记为Eb) 分别用于测验主区处理以及副区处理和主、副互作的显著性。对同一个二因素试验资料作自由度和平方和的分解,则可发现随机区组的误差项自由度和平方和分别为DFe、SSe,而裂区设计有两个误差项,其自由度和平方和分别为DFEa,DFEb, SSEa, SSEb。而区组,处理效应等各个变异项目的自由

samhuairen的专栏 8934

Java实现本地缓存的几种方式

Java实现本地缓存的几种方式

qinxun2008081的博客 1万+

手把手教你用CH582的USB0/1双接口玩转CherryUSB协议栈(附完整代码)

本文深入解析CH582双USB接口硬件架构,详细讲解如何通过CherryUSB协议栈实现双接口协同工作,包括中断处理设计、端点资源分配和复合设备描述符配置。提供HID+CDC复合设备实战案例,并分享高级调试技巧与性能优化方法,帮助开发者充分利用CH582的USB0/1双接口潜力。

weixin_42525637的博客 264

如何用Java设计一款本地缓存

缓存涉及的范围很广,如web页面缓存、客户端缓存、数据库缓存、磁盘缓存等。在后端程序中,缓存主要分为本地缓存和远端缓存,其中:远端缓存常见的有Redis、MongoDB等,本地缓存一般区分堆内缓存与堆外缓存缓存中可以容纳最大元素的数量当缓存存放的数据超过最大空间时,就需要根据淘汰算法来淘汰部分数据,以存放新到达的数据如果缓存满了,而又没有命中缓存,那么就会按照某一种策略,把缓存中的旧对象踢出,而把新的对象加入缓存池。而这些策略统称为替代策略(缓存算法)

weixin_46786684的博客 1403

保姆级教程:Java调用海康SDK实现录像回放与倍速播放(附完整代码)

本文详细介绍了如何使用Java调用海康SDK实现录像回放与倍速播放功能,包括开发环境配置、设备连接、流媒体处理、倍速播放实现及性能优化等关键步骤。通过完整的代码示例和实战经验,帮助开发者快速构建智能录像回放系统,提升开发效率。

weixin_29285605的博客 357

Moveit Servo学习

MoveIt Servo功能与配置指南 摘要:本文介绍了MoveIt Servo的功能特性,包括支持关节速度、末端执行器速度和位置控制,以及安全功能如奇点检查、碰撞检测等。通过示例展示了如何运行伺服控制demo,详细解析了关键节点和话题。最后以睿尔曼rml_63机器人为例,讲解了如何自定义伺服配置文件,包括环境设置、输入输出参数配置、平滑滤波器和规划场景监控等关键配置项。文章提供了从基础使用到自定义机器人配置的完整指导,帮助开发者实现高效的实时运动控制。

Fisher的博客 1178

Java中的本地缓存:4种实现方式

探索了Java本地缓存的多种实现方式,从手写缓存到Guava Cache、Caffeine、Ehcache和JetCache。每种方式都有自己的特点和适用场景。

呱牛 Just do IT 2293

视觉SLAM十四讲--第13讲 实践:设计SLAM系统(最详细的代码调试运行步骤)

最详细的代码调试运行避坑说明,也包含了我自己对代码的理解,如有理解错误的地方,欢迎指正交流。

记录和分享 1万+

本地缓存在Java中的实现方式

本文介绍了Java中实现本地缓存的多种方案及其适用场景。主要内容包括: 基础实现方式:ConcurrentHashMap线程安全缓存、LinkedHashMap实现LRU缓存 主流缓存框架:Guava Cache(功能全面)、Caffeine(高性能)、Ehcache(企业级功能) Spring集成方案:通过@Cacheable等注解实现声明式缓存 性能对比与选型建议:从小规模到企业级应用的不同选择 使用建议:合理设置缓存大小、过期策略,监控命中率,避免缓存污染等 文章强调应根据业务需求、数据规模和性能要求

weixin_50055999的博客 1628

别再到处找模板了!手把手教你用Vue3+Element Plus搭建一个‘能跑起来’的管理后台原型

本文详细介绍了如何利用Vue3和Element Plus在72小时内快速搭建一个功能完整的管理后台原型。从环境准备到项目获取、依赖安装、原型定制、数据模拟以及构建优化,手把手教你打造一个可直接演示的后台系统,特别适合创业者快速验证产品逻辑。

weixin_26854555的博客 183

后悔没早点知道!用AI只需四步,一次性讲清高质量论文引言写法(附公式+AI指令)

帮我写SCI 论文撰写引言的“研究背景与重要性”,要求从本研究所属领域的小背景切入,使用现在时态描述该领域的总体状况与发展趋势,突出其在学术研究、实际应用或社会层面的重要价值,并通过一个吸引人的钩子开头,如关键事实、现实问题或学术热点,引导读者关注本主题的重要性。梳理你所在领域和你研究相关的关键成果,但不能罗列所有文献,重点抓核心脉络,这部分最重要的实际上是让你了解别人的研究脉络,然后在每个关键的节点上,引用几篇关键参考文献作支撑,来体现你对领域的熟悉度就可以了。3)研究缺口模糊,没法突出问题的真实价值;

qq_64378896的博客 118

Java实现本地缓存、分布式缓存及多级缓存

Java实现本地缓存、分布式缓存及多级缓存

Mr.Cui的Java学习之路 2万+

Sigrity Power SI Model Extraction模式如何提取电源网络的S参数和阻抗操作指导(一)

34. 默认格式是BNP format格式,会被Sigrity识别的S参数文件,在仿真完成后,默认会在文件夹中生成bnp和ckt(MCP)文件,无需这步操作。由于port设置的ref Z是50ohm,实际上电源网络的阻抗是没有这么高的,所以S参数回损曲线是基本上全反射的一个状态。Sigrity PowerSI是频域电磁场仿真工具,以下图为例介绍如果用它观测电源的网络的S参数以及阻抗的频域曲线.42. Spice格式的加载进来图标是橙色的,BNP格式的图标是灰色的。

weixin_54787054的博客 1280

设计一个 Java 本地缓存组件

逐步设计一个 Java 本地缓存,综合考虑,轻量级设计,统一接口,基于 LRU/LFU,线程安全,兼容 Spring Cache 体系

zhangxin09的专栏 912

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

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

点击关注公众号,回复“1024”获取2TB学习资源!最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级缓存和二级缓存;一级缓存相对来说比较简单,功能比较齐全的是二级...

民工哥的博客 723

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

设计一个本地缓存,需要考虑哪些点

# 前言 最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级缓存和二级缓存;一级缓存相对来说比较简单,功能比较齐全的是二级缓存,基本上满足了一个缓存该有的功能;当然如果拿来和专门的缓存框架如ehcache来对比可能稍有差距;本文我们将来整理一下实现一个本地缓存都应该需要考虑哪些东西。 # 考虑点 考虑点主要在数据用何种方式存储,能存储多少数据,多余的数据如何处理等几个点,下面我们来详细的介绍每个考虑点,以及该如何去实现; 1.数据结构 首要考虑...

我真的睡不够 490

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

面试官:如何设计一个牛逼的本地缓存系统?

推荐阅读: 我总结了72份面试题,累计3170页,斩获了30+互联网公司offer(含BATJM) 2020首战告捷,这份Java面试神技Plus版,让我成功拿到了阿里、京东、字节跳动等大厂offer 膜拜!阿里内部都在强推的K8S(kubernetes)学习指南,不能再详细了 最近在看Mybatis的源码,刚好看到缓存这一块,Mybatis提供了一级缓存和二级缓存;一级缓存相对来说比较简单,功能比较齐全的是二级缓存,基本上满足了一个缓存该有的功能;当然如果拿来和专门的缓存框架如ehcach

Java_Jishu的博客 494
上一篇: 必懂系列!Java并发面试题
下一篇: 十分钟搞懂Java效率工具Lombok使用与原理
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值