前言

java从零手写实现redis(一)如何实现固定大小的缓存?

java从零手写实现redis(三)redis expire 过期原理

java从零手写实现redis(三)内存数据如何重启不失落?

本节,让咱们来一起学习一下如何实现相似 guava-cache 中的 removeListener 删除监听器,和相似 redis 中的慢日志监控的 slowListener。

删除监听器

阐明

咱们在两种场景下删除数据是对用户通明的:

(1)size 满了之后,进行数据淘汰。

(2)expire 过期时,革除数据。

这两个个性对用户原本应该是无感的,不过用户如果关怀的话,也能够通过增加删除监听器来获取到相干的变更信息。

实现思路

为了实现删除的监听,咱们须要找到删除的地位,而后调用监听器即可。

evict 驱除的场景

每次 put 数据时,都会校验 size 是否达到最大的限度,如果达到,则进行 evict 淘汰。

expire 过期的场景

用户指定 expire 工夫之后,回后盾异步执行刷新。

也存在惰性删除的场景。

接口定义

为了对立,咱们将所有的删除都定义对立的接口:

/** * 删除监听器接口 * * @author binbin.hou * @since 0.0.6 * @param <K> key * @param <V> value */public interface ICacheRemoveListener<K,V> {    /**     * 监听     * @param context 上下文     * @since 0.0.6     */    void listen(final ICacheRemoveListenerContext<K,V> context);}

内置实现

零碎内置的实现如下:

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());    }}

这个监听器是默认开启的,临时无奈敞开。

自定义

用户能够本人的须要,进行自定义实现:

public class MyRemoveListener<K,V> implements ICacheRemoveListener<K,V> {    @Override    public void listen(ICacheRemoveListenerContext<K, V> context) {        System.out.println("【删除提醒】可恶,我居然被删除了!" + context.key());    }}

测试

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

咱们指定 cache 的大小为1,设置咱们自定义的删除监听器。

这里的删除监听器能够增加多个。

日志

测试日志如下:

[DEBUG] [2020-09-30 19:32:54.617] [main] [c.g.h.c.c.s.l.r.CacheRemoveListener.listen] - Remove key: 2, value: 2, type: evict【删除提醒】可恶,我居然被删除了!2

慢操作监听器

阐明

redis 中会存储慢操作的相干日志信息,次要是由两个参数形成:

(1)slowlog-log-slower-than 预设阈值,它的单位是毫秒(1秒=1000000微秒)默认值是10000

(2)slowlog-max-len 最多存储多少条的慢日志记录

不过 redis 是间接存储到内存中,而且有长度限度。

依据理论工作体验,如果咱们能够增加慢日志的监听,而后有对应的存储或者报警,这样更加不便问题的剖析和疾速反馈。

所以咱们引入相似于删除的监听器。

实现思路

咱们解决所有的 cache 操作,并且记录对应的操作耗时。

如果耗时操作用户设置的工夫阈值,则调用慢操作监听器。

接口定义

为了保障接口的灵活性,每一个实现都能够定义本人的慢操作阈值,这样便于分级解决。

比方超过 100ms,用户能够抉择输入 warn 日志;超过 1s,可能影响到业务了,能够间接接入报警零碎。

public interface ICacheSlowListener {    /**     * 监听     * @param context 上下文     * @since 0.0.6     */    void listen(final ICacheSlowListenerContext context);    /**     * 慢日志的阈值     * @return 慢日志的阈值     * @since 0.0.9     */    long slowerThanMills();}

自定义监听器

实现接口 ICacheSlowListener

这里每一个监听器都能够指定本人的慢日志阈值,便于分级解决。

public class MySlowListener implements ICacheSlowListener {    @Override    public void listen(ICacheSlowListenerContext context) {        System.out.println("【慢日志】name: " + context.methodName());    }    @Override    public long slowerThanMills() {        return 0;    }}

应用

ICache<String, String> cache = CacheBs.<String,String>newInstance()        .addSlowListener(new MySlowListener())        .build();cache.put("1", "2");cache.get("1");
  • 测试成果
[DEBUG] [2020-09-30 17:40:11.547] [main] [c.g.h.c.c.s.i.c.CacheInterceptorCost.before] - Cost start, method: put[DEBUG] [2020-09-30 17:40:11.551] [main] [c.g.h.c.c.s.i.c.CacheInterceptorCost.after] - Cost end, method: put, cost: 10ms【慢日志】name: put[DEBUG] [2020-09-30 17:40:11.554] [main] [c.g.h.c.c.s.i.c.CacheInterceptorCost.before] - Cost start, method: get[DEBUG] [2020-09-30 17:40:11.554] [main] [c.g.h.c.c.s.i.c.CacheInterceptorCost.after] - Cost end, method: get, cost: 1ms【慢日志】name: get

理论工作中,咱们能够针对慢日志数据存储,便于前期剖析。

也能够间接接入报警零碎,及时反馈问题。

小结

监听器实现起来比较简单,然而对于使用者的作用是比拟大的。

文中次要讲述了思路,实现局部因为篇幅限度,没有全副贴出来。

开源地址:https://github.com/houbb/cache

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

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