【蓝因子教育】关于设计一个本地缓存

2025-02-25ASPCMS社区 - fjmyhfvclm

最近在看 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,每个线程都去执行一遍费时的计算,其实也是对资源的浪费;最好的办法是只有一个线程去执行,其他线程等待,计算一次就够了;但是此功能基本上都交给使用者来处理,很少有本地缓存有这种功能。

全部评论