前言
本节主要实现类似 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 的持久化模式,二者相辅相成,才能达到企业级别的缓存效果。
后续会引入这些特性。