前言

本节主要实现类似 redis 中的 rdb 持久化模式。

持久化的目的

之前存储的信息都是放在内存中的,如果断电或者应用重启,内容就会全部丢失,本节将实现将存储的信息持久化到磁盘,重启之后还在。

加载

首先实现一个简单的需求:
在缓存启动的时候,指定初始化加载的信息。

实现思路

在 cache 初始化的时候,直接设置对应的信息即可。

为了便于后期拓展,定义 ICacheLoad 接口。

1
2
3
public interface ICacheLoad<K, V> {
void load(final ICache<K, V> cache);
}

实现初始化策略

可以使用 Json 作为持久化信息格式,读取时只需要反序列化字符串,然后添加缓存即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class JsonCacheLoad<K, V> implements ICacheLoad<K, V> {

private final String path;

public JsonCacheLoad(String path) {
this.path = path;
}

@Override
public void load(ICache<K, V> cache) {
List<String> lines = FileUtil.readAllLines(path, StandardCharsets.UTF_8);
if (lines.isEmpty()) return;
for (String line : lines) {
if (line.isEmpty()) continue;
RDBPersistEntry<K, V> entry = JSON.parseObject(line, RDBPersistEntry.class);
cache.put(entry.getKey(), entry.getValue());
if (Objects.nonNull(entry.getExpire())) {
cache.expireAt(entry.getKey(), entry.getExpire());
}
}
}
}

持久化

上面就完成了 cache 的加载,基本上已经完成了 cache 持久化的一半。接下来就是将 cache 的内容持久化保存到文件或者数据库,便于初始化的时候加载。

接口定义

为了便于灵活替换,定义一个持久化接口。

1
2
3
4
5
6
7
public interface ICachePersist<K, V> {

/**
* 持久化操作
*/
void persist(final ICache<K, V> cache);
}

简单实现

先实现一个最简单的基于 json 的持久化,后期可以添加类似于 AOF 持久化模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class JsonCachePersist<K, V> implements ICachePersist<K, V> {

private final String path;

public JsonCachePersist(String path) {
this.path = path;
}

@Override
public void persist(ICache<K, V> cache) {
Set<Map.Entry<K,V>> entrySet = cache.entrySet();

// 创建文件
FileUtil.createFile(path);
// 清空文件
FileUtil.clear(path);

for(Map.Entry<K,V> entry : entrySet) {
K key = entry.getKey();
Long expireTime = cache.expire().expireTime(key);
RDBPersistEntry<K,V> persistEntry = new RDBPersistEntry<>();
persistEntry.setKey(key);
persistEntry.setValue(entry.getValue());
persistEntry.setExpire(expireTime);

String line = JSON.toJSONString(persistEntry);
FileUtil.write(path, line, StandardOpenOption.APPEND);
}
}
}

这里写文件采用 nio 的方式。

定时执行

上面定义好的持久化策略,但是没有提供触发方式。

可以采用对用户透明的设计方式:定时执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class InnerCachePersist<K, V> {

private static final Logger log = Logger.getLogger("com.chaofan.cache.support.persist.InnerCachePersist");

private final ICache<K, V> cache;

private final ICachePersist<K, V> persist;

private static final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();

public InnerCachePersist(ICache<K, V> cache, ICachePersist<K, V> persist) {
this.cache = cache;
this.persist = persist;

this.init();
}

private void init() {
service.scheduleAtFixedRate(() -> {
log.info("开始缓存持久化信息");
persist.persist(cache);
log.info("完成缓存持久化信息");
}, 0, 10, TimeUnit.MINUTES);
}
}

维护一个单线程执行器,然后设定开始后每 10 分钟执行一次持久化操作。

测试效果

1
2
3
4
5
6
7
8
public static void main(String[] args) throws InterruptedException {
ICache<String, String> cache = new CacheBuilder<String, String>()
.load(new MyCacheLoad())
.persist(CachePersists.json("1.rdb"))
.build();
System.out.println(cache.keySet());
TimeUnit.SECONDS.sleep(5);
}

文件内容如下:

1
{ "key": "1", "value": "1" }

小结

到这里,一个类似于 redis rdb 的持久化就简单模拟完成了。

但是对于 rdb 这里还有需要可优化点,比如 rdb 文件的压缩、格式的定义、CRC 校验等等。

redis 考虑到性能问题,还有 AOF 的持久化模式,二者相辅相成,才能达到企业级别的缓存效果。

后续会引入这些特性。