preface
We are From zero handwriting cache framework (1) to achieve a fixed size cache Our cache has been preliminarily implemented in.
We are Zero handwriting cache framework (1) implementation of expiration feature The expiration feature of key is implemented in.
In this section, let's learn how to implement the persistence mode similar to rdb in redis.
Purpose of persistence
The information we store is directly stored in memory. If we power off or restart the application, all the content will be lost.
Sometimes we want this information to remain after restart, just like redis restart.
Load load
explain
Before we implement persistence, let's look at a requirement:
How to specify the information of initialization loading when the cache is started.
Realization idea
This is not difficult. When initializing the cache, we can directly set the corresponding information.
api
To facilitate later expansion, the ICacheLoad interface is defined.
public interface ICacheLoad<K, V> { /** * Load cache information * @param cache cache * @since 0.0.7 */ void load(final ICache<K,V> cache); }
Custom initialization policy
When initializing, we put in two fixed messages.
public class MyCacheLoad implements ICacheLoad<String,String> { @Override public void load(ICache<String, String> cache) { cache.put("1", "1"); cache.put("2", "2"); } }
test
You only need to specify the corresponding loading implementation class during cache initialization.
ICache<String, String> cache = CacheBs.<String,String>newInstance() .load(new MyCacheLoad()) .build(); Assert.assertEquals(2, cache.size());
Persistence
explain
Initialization loading is introduced above. In fact, half of cache persistence has been completed.
Another thing we need to do is to persist the contents of the cache to a file or database for easy loading during initialization.
Interface definition
In order to facilitate flexible replacement, we define a persistent interface.
public interface ICachePersist<K, V> { /** * Persistent cache information * @param cache cache * @since 0.0.7 */ void persist(final ICache<K, V> cache); }
Simple implementation
We implement the simplest json based persistence. Of course, a persistence pattern similar to AOF can be added later.
public class CachePersistDbJson<K,V> implements ICachePersist<K,V> { /** * Database path * @since 0.0.8 */ private final String dbPath; public CachePersistDbJson(String dbPath) { this.dbPath = dbPath; } /** * Persistence * key Length key+value * The first space, get the length of the key, and then intercept it * @param cache cache */ @Override public void persist(ICache<K, V> cache) { Set<Map.Entry<K,V>> entrySet = cache.entrySet(); // create a file FileUtil.createFile(dbPath); // Empty file 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); } } }
Timed execution
The above defines a persistent strategy, but does not provide a corresponding trigger method.
We adopt a design method that is transparent to users: regular execution.
public class InnerCachePersist<K,V> { private static final Log log = LogFactory.getLog(InnerCachePersist.class); /** * Cache information * @since 0.0.8 */ private final ICache<K,V> cache; /** * Cache persistence strategy * @since 0.0.8 */ private final ICachePersist<K,V> persist; /** * Thread execution class * @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; // initialization this.init(); } /** * initialization * @since 0.0.8 */ private void init() { EXECUTOR_SERVICE.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { log.info("Start persisting cached information"); persist.persist(cache); log.info("Complete persistent cache information"); } catch (Exception exception) { log.error("File persistence exception", exception); } } }, 0, 10, TimeUnit.MINUTES); } }
The interval of timed execution is 10min.
test
We only need to specify our persistence policy when creating the 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);
In order to ensure the completion of file persistence, we slept for a while.
File effect
- 1.rdb
The generated file contents are as follows:
{"key":"2","value":"2"} {"key":"1","value":"1"}
Corresponding cache load
We only need to implement the following corresponding loading, parse the file, and then initialize the cache.
/** * Load policy - file path * @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); /** * File path * @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] Start processing path: {}", dbPath); if(CollectionUtil.isEmpty(lines)) { log.info("[load] path: {} File content is empty, return directly", dbPath); return; } for(String line : lines) { if(StringUtil.isEmpty(line)) { continue; } // implement // Simple types are OK, and complex deserialization will fail 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... } }
Then use it during initialization.
Summary
Here, we have completed a simple simulation of persistence similar to redis rdb.
However, for rdb, there are still some optimization points, such as rdb file compression, format definition, CRC verification and so on.
redis takes into account performance issues and the persistence mode of AOF. The two complement each other in order to achieve enterprise level caching effect.
We will introduce these features later.
If it's helpful to you, you're welcome to comment and collect a wave of attention~
Your encouragement is my greatest motivation~