Cache 用于实现一个可拓展的高性能本地缓存。
有人的地方,就有江湖。 有高性能的地方,就有 cache。
-
为日常开发提供一套简单易用的缓存框架
-
便于后期多级缓存开发
-
学以致用,开发一个类似于 redis 的本地缓存渐进式缓存框架
-
fluent 流式编程体验,纵享丝滑
-
支持 cache 固定大小
-
支持自定义 map 策略
-
支持自定义 expire 过期策略
-
支持自定义 evict 驱除策略(内置 FIFO/LRU 多种驱除策略)
-
支持 load 初始化和 persist 持久化(内置 RDB/AOF 模式)
-
支持自定义监听器
-
日志整合框架,自适应常见日志
v1.0.0 对原始代码进行大幅度调整,让整体更加简洁+方便拓展。
JDK1.7 及其以上版本
Maven 3.X 及其以上版本
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>cache-core</artifactId>
<version>1.0.1</version>
</dependency>
ICache<String, String> cache = CacheBs.<String,String>newInstance()
.size(2)
.build();
cache.put("1", "1");
cache.put("2", "2");
cache.put("3", "3");
cache.put("4", "4");
Assert.assertEquals(2, cache.size());
默认为先进先出的策略,此时输出 keys,内容如下:
[3, 4]
CacheBs
作为缓存的引导类,支持 fluent 写法,编程更加优雅便捷。
上述配置等价于:
ICache<String, String> cache = CacheBs.<String,String>newInstance()
.map(CacheMaps.<String,String>defaults())
.evict(CacheEvicts.<String, String>defaults())
.expire(CacheExpires.<String, String>defaults())
.load(CacheLoads.<String, String>defaults())
.persist(CachePersists.<String, String>defaults())
.interceptorList(CacheInterceptors.<String, String>defaults())
.size(2)
.build();
这些实现都有默认策略,同时全部支持自定义。
用于存储缓存的数据,简单起见,目前保留了 Map 接口的常用核心方法。
目前内置了几种策略,可以直接通过 CacheMaps
工具类创建。
策略 | 说明 |
---|---|
defaults() | 默认策略,目前为 concurrentHashMap |
hashMap() | 基于 HashMap 实现 |
concurrentHashMap() | 基于 ConcurrentHashMap |
当 map 的数据超过指定的数量时,对应的驱除策略。
目前内置了几种淘汰策略,可以直接通过 CacheEvicts
工具类创建。
策略 | 说明 |
---|---|
defaults() | 默认策略,目前为 FIFO |
none() | 没有任何淘汰策略 |
fifo() | 先进先出 |
lru() | 最基本的朴素 LRU 策略,性能一般 |
lruDoubleListMap() | 基于双向链表+MAP 实现的朴素 LRU,性能优于 lru |
lru2Q() | 基于 LRU 2Q 的改进版 LRU 实现,命中率优于朴素LRU |
lru2() | 基于 LRU-2 的改进版 LRU 实现,命中率优于 lru2Q |
类似 redis,支持通过 expireAt(key, linuxTime)
指定数据的过期时间。
会有定时调度对数据进行过期处理。
ICache<String, String> cache = CacheBs.<String,String>newInstance()
.size(3)
.build();
cache.put("1", "1");
cache.put("2", "2");
long now = System.currentTimeMillis();
cache.expireAt("1", now+40);
Assert.assertEquals(2, cache.size());
TimeUnit.MILLISECONDS.sleep(50);
Assert.assertEquals(1, cache.size());
System.out.println(cache.keySet());
cache.expireAt("1", now+40);
指定对应的 key 在 40ms 后过期。
目前内置了几种策略,可以直接通过 CacheExpires
工具类创建。
策略 | 说明 |
---|---|
defaults() | 默认策略,目前为 random |
none() | 没有任何过期策略 |
random() | 随机 key,类似 redis |
sort() | 按照过期时间排序处理,需要额外的空间 |
有时候我们需要在 cache 初始化的时候,添加对应的数据初始化。
后期可以从文件等地方加载数据。
建议和 persist 持久化配套使用。
目前内置了几种策略,可以直接通过 CacheLoads
工具类创建。
策略 | 说明 |
---|---|
defaults() | 默认策略,目前为 none |
none() | 空实现 |
aof() | AOF 模式 |
dbJson() | RDB 模式 |
继承 AbstractCacheLoad
抽象类即可。
public class MyCacheLoad extends AbstractCacheLoad<String,String> {
@Override
public void doLoad() {
super.context.map().put("1", "1");
super.context.map().put("2", "2");
}
}
我们在缓存初始化的时候,放入 2 个元素。
ICache<String, String> cache = CacheBs.<String,String>newInstance()
.load(new MyCacheLoad())
.build();
Assert.assertEquals(2, cache.size());
如果我们只是把文件放在内存中,应用重启信息就丢失了。
有时候我们希望这些 key/value 信息可以持久化,存储到文件或者 database 中。
CachePersists.<String, String>dbJson("1.rdb")
指定将数据文件持久化到文件中。
定期执行,暂时全量持久化的间隔为 10min,后期考虑支持更多配置。
public void persistTest() throws InterruptedException {
ICache<String, String> cache = CacheBs.<String,String>newInstance()
.load(new MyCacheLoad())
.persist(CachePersists.<String, String>dbJson("1.rdb"))
.build();
Assert.assertEquals(2, cache.size());
TimeUnit.SECONDS.sleep(5);
}
- 1.rdb
文件内容如下:
{"key":"2","value":"2"}
{"key":"1","value":"1"}
存储之后,可以使用对应的加载器读取文件内容:
ICache<String, String> cache = CacheBs.<String,String>newInstance()
.load(CacheLoads.<String, String>dbJson("1.rdb"))
.build();
Assert.assertEquals(2, cache.size());
目前内置了几种策略,可以直接通过 CachePersists
工具类创建。
策略 | 说明 |
---|---|
defaults() | 默认策略,目前为 none |
none() | 空实现 |
aof() | AOF 模式 |
dbJson() | RDB 模式 |
为了方便我们针对常见的操作进行监听,暴露了操作的拦截器接口。
备注:这个后续考虑拓展为类似于 dubbo 的拦截器,可能会把这个接口隐藏掉。暴露新的接口。
默认的主要是功能性的策略,在 CacheInterceptors.defaults()
,主要包含了如下4个。
策略 | 说明 |
---|---|
commonCost() | 通用参数、耗时 |
evict() | 驱逐相关的监听 |
aof() | AOF 模式监听 |
refresh() | expire 有效性刷新监听 |
其中后3个是核心的功能相关,需要保留。支持自定义拓展。
-
用拦截器 chain 代替目前的循环 filter,让编码更加自然
-
引入异步,考虑参考 async
-
过期策略添加随机返回
-
expireAfterWrite()
-
expireAfterAccess()
- AOF 混合 RDB
-
命中率
-
keys 数量
-
evict 数量
-
expire 数量
-
耗时统计
-
异步 callable 操作
-
spring 整合
提供 @Cacheable
系列注解
-
文件压缩
-
独立服务端
提供类似于 redis-server + redis-client 的拆分,便于独立于应用作为服务存在。
java从零手写实现redis(一)如何实现固定大小的缓存?
java从零手写实现redis(三)redis expire 过期原理
java从零手写实现redis(三)内存数据如何重启不丢失?
java从零手写实现redis(五)过期策略的另一种实现思路
java从零手写实现redis(六)AOF 持久化原理详解及实现
java从零手写实现redis(七)LRU 缓存淘汰策略详解
java从零开始手写redis(八)朴素 LRU 淘汰算法性能优化
java从零开始手写redis(九)LRU 缓存淘汰算法如何避免缓存污染
java从零开始手写redis(十)缓存淘汰算法 LFU 最少使用频次
java从零开始手写redis(十一)缓存淘汰算法 COLOK 算法
java从零开始手写redis(十二)过期策略如何实现随机 keys 淘汰
java从零开始手写redis(十三)redis渐进式rehash详解
java从零开始手写redis(十四)JDK HashMap 源码解析
java从零开始手写redis(十四)JDK ConcurrentHashMap 源码解析
java从零开始手写redis(十五)实现自己的 HashMap
java从零开始手写redis(十六)实现渐进式 rehash map
java从零开始手写redis(十七)v1.0.0 全新版本架构优化+拓展性增强
缓存实战(1)缓存雪崩、缓存击穿和缓存穿透入门简介及解决方案
缓存实战(2)布隆过滤器是啥?guava 的 BloomFilter 使用
缓存实战(3)让你彻底搞懂布隆过滤器!实现一个自己的BloomFilter
缓存实战(4)bloom filter 使用最佳实践,让你少踩坑
下面是一些缓存系列的开源矩阵规划。
名称 | 介绍 | 状态 |
---|---|---|
resubmit | 防止重复提交核心库 | 已开源 |
rate-limit | 限流核心库 | 已开源 |
cache | 手写渐进式 redis | 已开源 |
lock | 开箱即用的分布式锁 | 已开源 |
common-cache | 通用缓存标准定义 | 已开源 |
redis-config | 兼容各种常见的 redis 配置模式 | 已开源 |
quota-server | 限额限次核心服务 | 待开始 |
quota-admin | 限额限次控台 | 待开始 |
flow-control-server | 流控核心服务 | 待开始 |
flow-control-admin | 流控控台 | 待开始 |