关于redis:从零开始手写-redis三内存数据重启后如何不丢失

1次阅读

共计 3909 个字符,预计需要花费 10 分钟才能阅读完成。

前言

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

咱们后续将陆续引入这些个性。

对你有帮忙的话,欢送点赞评论珍藏关注一波~

你的激励,是我最大的能源~

正文完
 0