前言
咱们在 从零手写 cache 框架(一)实现固定大小的缓存 中曾经初步实现了咱们的 cache。
咱们在 从零手写 cache 框架(一)实现过期个性 中实现了 key 的过期个性。
本节,让咱们来一起学习一下如何实现相似 redis 中的 rdb 的长久化模式。
长久化的目标
咱们存储的信息都是间接放在内存中的,如果断电或者利用重启,那么内容就全副失落了。
有时候咱们心愿这些信息重启之后还在,就像 redis 重启一样。
load 加载
阐明
在实现长久化之前,咱们来看一下一个简略的需要:
如何在缓存启动的时候,指定初始化加载的信息。
实现思路
这个也不难,咱们在 cache 初始化的时候,间接设置对应的信息即可。
api
为了便于前期拓展,定义 ICacheLoad 接口。
public interface ICacheLoad<K, V> {
/**
* 加载缓存信息
* @param cache 缓存
* @since 0.0.7
*/
void load(final ICache<K,V> cache);
}
自定义初始化策略
咱们在初始化的时候,放入 2 个固定的信息。
public class MyCacheLoad implements ICacheLoad<String,String> {
@Override
public void load(ICache<String, String> cache) {cache.put("1", "1");
cache.put("2", "2");
}
}
测试
只须要在缓存初始化的时候,指定对应的加载实现类即可。
ICache<String, String> cache = CacheBs.<String,String>newInstance()
.load(new MyCacheLoad())
.build();
Assert.assertEquals(2, cache.size());
长久化
阐明
下面先介绍初始化加载,其实曾经实现了 cache 长久化的一半。
咱们要做的另一件事,就是将 cache 的内容长久化到文件或者数据库,便于初始化的时候加载。
接口定义
为了便于灵便替换,咱们定义一个长久化的接口。
public interface ICachePersist<K, V> {
/**
* 长久化缓存信息
* @param cache 缓存
* @since 0.0.7
*/
void persist(final ICache<K, V> cache);
}
简略实现
咱们实现一个最简略的基于 json 的长久化,当然前期能够增加相似于 AOF 的长久化模式。
public class CachePersistDbJson<K,V> implements ICachePersist<K,V> {
/**
* 数据库门路
* @since 0.0.8
*/
private final String dbPath;
public CachePersistDbJson(String dbPath) {this.dbPath = dbPath;}
/**
* 长久化
* key 长度 key+value
* 第一个空格,获取 key 的长度,而后截取
* @param cache 缓存
*/
@Override
public void persist(ICache<K, V> cache) {Set<Map.Entry<K,V>> entrySet = cache.entrySet();
// 创立文件
FileUtil.createFile(dbPath);
// 清空文件
FileUtil.truncate(dbPath);
for(Map.Entry<K,V> entry : entrySet) {K key = entry.getKey();
Long expireTime = cache.expire().expireTime(key);
PersistEntry<K,V> persistEntry = new PersistEntry<>();
persistEntry.setKey(key);
persistEntry.setValue(entry.getValue());
persistEntry.setExpire(expireTime);
String line = JSON.toJSONString(persistEntry);
FileUtil.write(dbPath, line, StandardOpenOption.APPEND);
}
}
}
定时执行
下面定义好了一种长久化的策略,然而没有提供对应的触发形式。
咱们就采纳对用户通明的设计形式:定时执行。
public class InnerCachePersist<K,V> {private static final Log log = LogFactory.getLog(InnerCachePersist.class);
/**
* 缓存信息
* @since 0.0.8
*/
private final ICache<K,V> cache;
/**
* 缓存长久化策略
* @since 0.0.8
*/
private final ICachePersist<K,V> persist;
/**
* 线程执行类
* @since 0.0.3
*/
private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
public InnerCachePersist(ICache<K, V> cache, ICachePersist<K, V> persist) {
this.cache = cache;
this.persist = persist;
// 初始化
this.init();}
/**
* 初始化
* @since 0.0.8
*/
private void init() {EXECUTOR_SERVICE.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {log.info("开始长久化缓存信息");
persist.persist(cache);
log.info("实现长久化缓存信息");
} catch (Exception exception) {log.error("文件长久化异样", exception);
}
}
}, 0, 10, TimeUnit.MINUTES);
}
}
定时执行的工夫距离为 10min。
测试
咱们只须要在创立 cache 时,指定咱们的长久化策略即可。
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"}
对应的缓存加载
咱们只须要实现以下对应的加载即可,解析文件,而后初始化 cache。
/**
* 加载策略 - 文件门路
* @author binbin.hou
* @since 0.0.8
*/
public class CacheLoadDbJson<K,V> implements ICacheLoad<K,V> {private static final Log log = LogFactory.getLog(CacheLoadDbJson.class);
/**
* 文件门路
* @since 0.0.8
*/
private final String dbPath;
public CacheLoadDbJson(String dbPath) {this.dbPath = dbPath;}
@Override
public void load(ICache<K, V> cache) {List<String> lines = FileUtil.readAllLines(dbPath);
log.info("[load] 开始解决 path: {}", dbPath);
if(CollectionUtil.isEmpty(lines)) {log.info("[load] path: {} 文件内容为空,间接返回", dbPath);
return;
}
for(String line : lines) {if(StringUtil.isEmpty(line)) {continue;}
// 执行
// 简略的类型还行,简单的这种反序列化会失败
PersistEntry<K,V> entry = JSON.parseObject(line, PersistEntry.class);
K key = entry.getKey();
V value = entry.getValue();
Long expire = entry.getExpire();
cache.put(key, value);
if(ObjectUtil.isNotNull(expire)) {cache.expireAt(key, expire);
}
}
//nothing...
}
}
而后在初始化时应用即可。
小结
到这里,咱们一个相似于 redis rdb 的长久化就简略模仿实现了。
然而对于 rdb 这里还有须要可优化点,比方 rdb 文件的压缩、格局的定义、CRC 校验等等。
redis 思考到性能问题,还有 AOF 的长久化模式,二者相辅相成,能力达到企业级别的缓存成果。
咱们后续将陆续引入这些个性。
对你有帮忙的话,欢送点赞评论珍藏关注一波~
你的激励,是我最大的能源~