preface

Redis (a) how to implement fixed size cache?

Java from zero handwriting implementation of Redis (c) Redis expire principle

Java from zero handwriting implementation redis (three) how to restart memory data is not lost?

In this section, let’s learn how to implement a removeListener deletion listener like guava-Cache and a slowListener like redis.

Delete listeners

instructions

We delete data transparently to users in two scenarios:

(1) After the size is full, the data will be eliminated.

(2) Clear data when expire expires.

These two features are supposed to be insensitive to the user, but the user can also add and remove listeners to get relevant change information if they care.

Implementation approach

To implement the deletion listening, we need to find the deletion location, and then call the listener.

Evict evict scene

Every time data is put, it verifies whether the size reaches the maximum limit. If so, EVICT is eliminated.

Expire scenario

After the user specifies the EXPIRE time, the refresh is performed asynchronously back in the background.

Lazy deletion scenarios also exist.

The interface definition

For uniformity, we define a unified interface for all deletions:

/** * Delete listener interface **@author binbin.hou
 * @since 0.0.6
 * @param <K> key
 * @param <V> value
 */
public interface ICacheRemoveListener<K.V> {

    /** * monitor *@paramContext *@since0.0.6 * /
    void listen(final ICacheRemoveListenerContext<K,V> context);

}
Copy the code

Built-in implementation

The system built-in implementation is as follows:

public class CacheRemoveListener<K.V> implements ICacheRemoveListener<K.V> {

    private static final Log log = LogFactory.getLog(CacheRemoveListener.class);

    @Override
    public void listen(ICacheRemoveListenerContext<K, V> context) {
        log.debug("Remove key: {}, value: {}, type: {}", context.key(), context.value(), context.type()); }}Copy the code

This listener is enabled by default and cannot be turned off for now.

The custom

Users can customize their own needs:

public class MyRemoveListener<K.V> implements ICacheRemoveListener<K.V> {

    @Override
    public void listen(ICacheRemoveListenerContext<K, V> context) {
        System.out.println("[Delete hint] Damn, I've been deleted!"+ context.key()); }}Copy the code

test

ICache<String, String> cache = CacheBs.<String,String>newInstance()
        .size(1)
        .addRemoveListener(new MyRemoveListener<String, String>())
        .build();

cache.put("1"."1");
cache.put("2"."2");
Copy the code

We specify a cache size of 1 and set our custom delete listener.

Multiple delete listeners can be added here.

The log

The test log is as follows:

[the DEBUG] [the 2020-09-30 19:32:54. 617] [the main] [C.G.H.C.C.S.L.R.C acheRemoveListener. Listen] - Remove the key: 2, value: 2, type: Evict [Delete hint] Damn, I've been deleted! 2Copy the code

Slow operation listener

instructions

Redis stores logs related to slow operations, consisting of two parameters:

Slowlog-log-slower than the preset threshold which is in milliseconds (1 second =1000000 microseconds) the default value is 10000

Slowlog-max-len specifies the maximum number of slowlog records to store

However, redis is stored directly in memory and has a length limit.

According to the actual working experience, if we can add the monitoring of slow logs, and then have corresponding storage or alarm, it will be more convenient for problem analysis and quick feedback.

So we introduce something like a delete listener.

Implementation approach

We process all cache operations and record the corresponding operation time.

If the time threshold set by the time-consuming operation user, the slow operation listener is invoked.

The interface definition

To ensure the flexibility of the interface, each implementation can define its own slow operation threshold, which facilitates hierarchical processing.

For example, if the logs exceed 100ms, you can choose to output warn logs. Longer than 1s, services may be affected, you can directly access the alarm system.

public interface ICacheSlowListener {

    /** * monitor *@paramContext *@since0.0.6 * /
    void listen(final ICacheSlowListenerContext context);

    /** * Slow log threshold *@returnSlow log threshold *@since0.0.9 * /
    long slowerThanMills(a);

}
Copy the code

Custom listeners

Implement the interface ICacheSlowListener

Here each listener can specify its own slow logging threshold for hierarchical processing.

public class MySlowListener implements ICacheSlowListener {

    @Override
    public void listen(ICacheSlowListenerContext context) {
        System.out.println("Name:" + context.methodName());
    }

    @Override
    public long slowerThanMills(a) {
        return 0; }}Copy the code

use

ICache<String, String> cache = CacheBs.<String,String>newInstance()
        .addSlowListener(new MySlowListener())
        .build();

cache.put("1"."2");
cache.get("1");
Copy the code
  • The test results
[the DEBUG] [the 2020-09-30 17:40:11. 547] [the main] [C.G.H.C.C.S.I.C.C acheInterceptorCost. Before] - Cost start method: Put [DEBUG] [the 2020-09-30 17:40:11. 551] [the main] [C.G.H.C.C.S.I.C.C acheInterceptorCost. After] - Cost end, method: Put, cost: 10ms [slow log] name: Put [DEBUG] [the 2020-09-30 17:40:11. 554] [the main] [C.G.H.C.C.S.I.C.C acheInterceptorCost. Before] - Cost start method: Get [the DEBUG] [the 2020-09-30 17:40:11. 554] [the main] [C.G.H.C.C.S.I.C.C acheInterceptorCost. After] - Cost end, method: Get, cost: 1ms [slow log] name: getCopy the code

In practice, we can store slow log data for later analysis.

Can also be directly connected to the alarm system, timely feedback problems.

summary

Listeners are relatively simple to implement, but of great value to users.

The article mainly tells about the idea, the realization part because of the space limitation, did not post all.

Open source address: github.com/houbb/cache

If you found this article helpful, please feel free to like it

Your encouragement, is my biggest motivation ~