前言

我们在 从零手写 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 的持久化模式,二者相辅相成,才能达到企业级别的缓存效果。

我们后续将陆续引入这些特性。

对你有帮助的话,欢迎点赞评论收藏关注一波~

你的鼓励,是我最大的动力~

从零开始手写 redis(三)内存数据重启后如何不丢失?的更多相关文章

  1. java 从零开始手写 RPC (05) reflect 反射实现通用调用之服务端

    通用调用 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何 ...

  2. java 从零开始手写 RPC (03) 如何实现客户端调用服务端?

    说明 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 写完了客户端和服务端,那么如何实现客户端和服务端的 ...

  3. java 从零开始手写 RPC (04) -序列化

    序列化 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何实 ...

  4. java 从零开始手写 RPC (07)-timeout 超时处理

    <过时不候> 最漫长的莫过于等待 我们不可能永远等一个人 就像请求 永远等待响应 超时处理 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RP ...

  5. redis设置密码,解决重启后密码丢失及自启服务配置

    一.安装redis redis3.0及redisManage管理工具 链接:https://pan.baidu.com/s/1p5EWeF2Jgsw9xOE1ADMmRg 提取码:thyf 二.red ...

  6. 将本地图片数据制作成内存对象数据集|tensorflow|手写数字制作成内存对象数据集|tf队列|线程

      样本说明: tensorflow经典实例之手写数字识别.MNIST数据集. 数据集dir名称 每个文件夹代表一个标签label,每个label中有820个手写数字的图片 标签label为0的文件夹 ...

  7. C基础 带你手写 redis adlist 双向链表

    引言 - 导航栏目 有些朋友可能对 redis 充满着数不尽的求知欲, 也许是 redis 属于工作, 交流(面试)的大头戏, 不得不 ... 而自己当下对于 redis 只是停留在会用层面, 细节层 ...

  8. 关于布隆过滤器,手写你真的知其原理吗?让我来带你手写redis布隆过滤器。

    说到布隆过滤器不得不提到,redis, redis作为现在主流的nosql数据库,备受瞩目:它的丰富的value类型,以及它的偏向计算向数据移动属性减少IO的成本问题.备受开发人员的青睐.通常我们使用 ...

  9. C基础 带你手写 redis sds

    前言 - Simple Dynamic Strings  antirez 想统一 Redis,Disque,Hiredis 项目中 SDS 代码, 因此构建了这个项目 https://github.c ...

  10. 手写Pinia存储的数据持久化插件

    Pinia和Vuex的通病 Pinia和vuex的通病就是,页面刷新会导致数据丢失 解决通病 一.新建store import { defineStore } from 'pinia' //单独存放S ...

随机推荐

  1. SV 设计特性

    过程语句块特性 ABC 过程块语句 always_comb 防止多驱动的问题:赋值块左侧的语句无法被另一个过程块赋值 if语句没有写else,sv会提示警告,sv认为是latch always不会再仿 ...

  2. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2024.01.23)

    一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...

  3. [转帖]【oracle】oracle查询表存储大小和表空间大小

    目录 查看表分配的物理空间大小 查看表实际存储空间大小 查看每个表空间的大小 查看表空间大小及使用率 查看数据库中数据文件信息 查看临时表空间信息 oracle表大小有两种含义,即表分配的空间大小和实 ...

  4. [转帖]tidb数据库5.4.3和6.5.3版本性能测试对比

    https://tidb.net/blog/5454621f   一.测试需求: 基于历史原因,我们的业务数据库一直使用5.4.3,最近由于研发提出需求:需要升级到6.5.3版本,基于版本不同,需要做 ...

  5. [转帖]tidb backup

    https://docs.pingcap.com/zh/tidb/v4.0/sql-statement-restore BACKUP 语句使用的引擎与 BR 相同,但备份过程是由 TiDB 本身驱动, ...

  6. [转帖]TiFlash 面向编译器的自动向量化加速

    作者:朱一帆 目录​ SIMD 介绍 SIMD 函数派发方案 面向编译器的优化 SIMD 介绍​ SIMD 是重要的重要的程序加速手段.CMU DB 组在 Advanced Database Syst ...

  7. [转帖]s3fs把 s3-like 对象存储挂载到本地

    s3fs把 s3-like 对象存储挂载到本地 s3fs把 s3-like 对象存储挂载到本地 s3fs-fuse 是一个采用 c++ 开发的开源应用,它的作用是可以将 AWS S3 以及兼容 S3 ...

  8. 多个物理磁盘挂载到同一目录的方法 (lvm 软raid)

    多个物理磁盘挂载到同一目录的方法 (lvm 软raid) 背景 公司里面的一台申威3231的机器 因为这个机器的raid卡没有操作界面. 所以只能够通过命令行方式创建raid 自己这一块比较菜, 想着 ...

  9. 【转帖】调教LLaMA类模型没那么难,LoRA将模型微调缩减到几小时

    https://www.thepaper.cn/newsDetail_forward_23250236 LoRA 微调方法,随着大模型的出现而走红. 最近几个月,ChatGPT 等一系列大语言模型(L ...

  10. [转帖]Linux系统中的Page cache和Buffer cache

    Free命令显示内存 首先,我们来了解下内存的使用情况: Mem:表示物理内存统计 total:表示物理内存总量(total = used + free) used:表示总计分配给缓存(包含buffe ...