SpringBoot如何监控Redis中某个Key的变化(自定义监听器)

 更新时间:2021年09月14日 15:27:13   作者:你是小KS  
这篇文章主要介绍了SpringBoot如何监控Redis中某个Key的变化(自定义监听器),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

Java技术迷

SpringBoot 监控Redis中某个Key的变化

1.声明

当前内容主要为本人学习和基本测试,主要为监控redis中的某个key的变化(感觉网上的都不好,所以自己看Spring源码直接写一个监听器)

个人参考:

2.基本理念

网上的demo的缺点

  • 使用继承KeyExpirationEventMessageListener只能监听当前key消失的事件
  • 使用KeyspaceEventMessageListener只能监听所有的key事件

总体来说,不能监听某个特定的key的变化(某个特定的redis数据库),具有缺陷

直接分析获取可以操作的步骤

查看KeyspaceEventMessageListener的源码解决问题

基本思想

  • 创建自己的主题(用来监听某个特定的key)
  • 创建监听器实现MessageListener
  • 注入自己的配置信息

查看其中的方法(init方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void init() {
        if (StringUtils.hasText(keyspaceNotificationsConfigParameter)) {
            RedisConnection connection = listenerContainer.getConnectionFactory().getConnection();
            try {
                Properties config = connection.getConfig("notify-keyspace-events");
                if (!StringUtils.hasText(config.getProperty("notify-keyspace-events"))) {
                    connection.setConfig("notify-keyspace-events", keyspaceNotificationsConfigParameter);
                }
            } finally {
                connection.close();
            }
        }
        doRegister(listenerContainer);
    }
    /**
     * Register instance within the container.
     *
     * @param container never {@literal null}.
     */
    protected void doRegister(RedisMessageListenerContainer container) {
        listenerContainer.addMessageListener(this, TOPIC_ALL_KEYEVENTS);
    }

主要操作如下

  • 向redis中写入配置notify-keyspace-events并设置为EA
  • 向RedisMessageListenerContainer中添加本身这个监听器并指定监听主题

所以本人缺少的就是这个主题表达式和监听的notify-keyspace-events配置

直接来到redis的官方文档找到如下内容

所以直接选择的是:__keyspace@0__:myKey,使用的模式为KEA

所有的工作全部完毕后开始实现监听

3.实现和创建监听

创建监听类:RedisKeyChangeListener

本类中主要监听redis中数据库0的myKey这个key

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.nio.charset.Charset;
import java.util.Properties;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.listener.KeyspaceEventMessageListener;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import org.springframework.util.StringUtils;
/**
 *
 * @author hy
 * @createTime 2021-05-01 08:53:19
 * @description 期望是可以监听某个key的变化,而不是失效
 *
 */
public class RedisKeyChangeListener implements MessageListener/* extends KeyspaceEventMessageListener */ {
    private final String listenerKeyName; // 监听的key的名称
    private static final Topic TOPIC_ALL_KEYEVENTS = new PatternTopic("__keyevent@*"); //表示只监听所有的key
    private static final Topic TOPIC_KEYEVENTS_SET = new PatternTopic("__keyevent@0__:set"); //表示只监听所有的key
    private static final Topic TOPIC_KEYNAMESPACE_NAME = new PatternTopic("__keyspace@0__:myKey"); // 不生效
    // 监控
    //private static final Topic TOPIC_KEYEVENTS_NAME_SET_USELESS = new PatternTopic("__keyevent@0__:set myKey");
    private String keyspaceNotificationsConfigParameter = "KEA";
    public RedisKeyChangeListener(RedisMessageListenerContainer listenerContainer, String listenerKeyName) {
        this.listenerKeyName = listenerKeyName;
        initAndSetRedisConfig(listenerContainer);
    }
    public void initAndSetRedisConfig(RedisMessageListenerContainer listenerContainer) {
        if (StringUtils.hasText(keyspaceNotificationsConfigParameter)) {
            RedisConnection connection = listenerContainer.getConnectionFactory().getConnection();
            try {
                Properties config = connection.getConfig("notify-keyspace-events");
                if (!StringUtils.hasText(config.getProperty("notify-keyspace-events"))) {
                    connection.setConfig("notify-keyspace-events", keyspaceNotificationsConfigParameter);
                }
            } finally {
                connection.close();
            }
        }
        // 注册消息监听
        listenerContainer.addMessageListener(this, TOPIC_KEYNAMESPACE_NAME);
    }
    @Override
    public void onMessage(Message message, byte[] pattern) {
        System.out.println("key发生变化===》" + message);
        byte[] body = message.getBody();
        String string = new String(body, Charset.forName("utf-8"));
        System.out.println(string);
    }
}

其实就改了几个地方…

4.基本demo的其他配置

1.RedisConfig配置类

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Configuration
@PropertySource(value = "redis.properties")
@ConditionalOnClass({ RedisConnectionFactory.class, RedisTemplate.class })
public class RedisConfig {
    @Autowired
    RedisProperties redisProperties;
    /**
     *
     * @author hy
     * @createTime 2021-05-01 08:40:59
     * @description 基本的redisPoolConfig
     * @return
     *
     */
    private JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(redisProperties.getMaxIdle());
        config.setMaxTotal(redisProperties.getMaxTotal());
        config.setMaxWaitMillis(redisProperties.getMaxWaitMillis());
        config.setTestOnBorrow(redisProperties.getTestOnBorrow());
        return config;
    }
    /**
     * @description 创建redis连接工厂
     */
    @SuppressWarnings("deprecation")
    private JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory factory = new JedisConnectionFactory(
                new JedisShardInfo(redisProperties.getHost(), redisProperties.getPort()));
        factory.setPassword(redisProperties.getPassword());
        factory.setTimeout(redisProperties.getTimeout());
        factory.setPoolConfig(jedisPoolConfig());
        factory.setUsePool(redisProperties.getUsePool());
        factory.setDatabase(redisProperties.getDatabase());
        return factory;
    }
    /**
     * @description 创建RedisTemplate 的操作类
     */
    @Bean
    public StringRedisTemplate getRedisTemplate() {
        StringRedisTemplate redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        redisTemplate.setEnableTransactionSupport(true);
        return redisTemplate;
    }
     
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer() throws Exception {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(jedisConnectionFactory());      
        return container;
    }
    // 创建基本的key监听器
    /*  */
    @Bean
    public RedisKeyChangeListener redisKeyChangeListener() throws Exception {
        RedisKeyChangeListener listener = new RedisKeyChangeListener(redisMessageListenerContainer(),"");
        return listener;
    }
}

其中最重要的就是RedisMessageListenerContainer 和RedisKeyChangeListener

2.另外的RedisProperties类,加载redis.properties文件成为对象的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 *
 * @author hy
 * @createTime 2021-05-01 08:38:26
 * @description 基本的redis的配置类
 *
 */
@ConfigurationProperties(prefix = "redis")
public class RedisProperties {
    private String host;
    private Integer port;
    private Integer database;
    private Integer timeout;
    private String password;
    private Boolean usePool;
    private Integer maxTotal;
    private Integer maxIdle;
    private Long maxWaitMillis;
    private Boolean testOnBorrow;
    private Boolean testWhileIdle;
    private Integer timeBetweenEvictionRunsMillis;
    private Integer numTestsPerEvictionRun;
    // 省略get\set方法
}

省略其他代码

5.基本测试

创建一个key,并修改发现变化

在这里插入图片描述 在这里插入图片描述

可以发现返回的是这个key执行的方法(set),如果使用的是keyevent方式那么返回的就是这个key的名称

6.小结一下

1.监听redis中的key的变化主要利用redis的机制来实现(本身就是发布/订阅)

2.默认情况下是不开启的,原因有点耗cpu

3.实现的时候需要查看redis官方文档和SpringBoot的源码来解决实际的问题

SpringBoot自定义监听器

原理

Listener按照监听的对象的不同可以划分为:

  • 监听ServletContext的事件监听器,分别为:ServletContextListener、ServletContextAttributeListener。Application级别,整个应用只存在一个,可以进行全局配置。
  • 监听HttpSeesion的事件监听器,分别为:HttpSessionListener、HttpSessionAttributeListener。Session级别,针对每一个对象,如统计会话总数。
  • 监听ServletRequest的事件监听器,分别为:ServletRequestListener、ServletRequestAttributeListener。Request级别,针对每一个客户请求。

示例

第一步:创建项目,添加依赖

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
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.eclipse.jdt.core.compiler</groupId>
    <artifactId>ecj</artifactId>
    <version>4.6.1</version>
</dependency>

第二步:自定义监听器

1
2
3
4
5
6
7
8
9
10
11
@WebListener
public class MyServletRequestListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("Request监听器,销毁");
    }
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("Request监听器,初始化");
    }
}

第三步:定义Controller

1
2
3
4
5
6
7
@RestController
public class DemoController {
    @RequestMapping("/fun")
    public void fun(){
        System.out.println("fun");
    }
}

第四步:在程序执行入口类上面添加注解

1
@ServletComponentScan

部署项目,运行查看效果:

在这里插入图片描述

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:https://blog.csdn.net/weixin_45492007/article/details/116326073

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • 带你快速入门掌握Spring的那些注解使用

    带你快速入门掌握Spring的那些注解使用

    注解是个好东西,注解是Java语法,被Java编译器检查,可以减少配置错误,这篇文章主要给大家介绍了关于Spring的那些注解使用的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-02-02
  • Springboot项目打war包docker包找不到resource下静态资源的解决方案

    Springboot项目打war包docker包找不到resource下静态资源的解决方案

    今天小编就为大家分享一篇关于Springboot项目打war包docker包找不到resource下静态资源的解决方案,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • Java图书管理系统,课程设计必用(源码+文档)

    Java图书管理系统,课程设计必用(源码+文档)

    本系统采用Java,MySQL 作为系统数据库,重点开发并实现了系统各个核心功能模块,包括采编模块、典藏模块、基础信息模块、流通模块、期刊模块、查询模块、评论模块、系统统计模块以及帮助功能模块
    2021-06-06
  • MyBatis Plus如何实现获取自动生成主键值

    MyBatis Plus如何实现获取自动生成主键值

    这篇文章主要介绍了MyBatis Plus如何实现获取自动生成主键值问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • MyBatis XPathParser解析器使用范例详解

    MyBatis XPathParser解析器使用范例详解

    这篇文章主要介绍了关于MyBatis中解析器XPathParser的实际使用实践,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2022-07-07
  • Springboot+AOP实现时间参数格式转换

    Springboot+AOP实现时间参数格式转换

    前端传过来的时间参数,后端可以自定义时间格式转化使用,这样想转成什么就转成什么。本文将利用自定义注解AOP实现时间参数格式转换,感兴趣的可以了解一下
    2022-04-04
  • Java线程生命周期的终止与复位

    Java线程生命周期的终止与复位

    这篇文章主要介绍了Java线程生命周期的终止与复位,Java的线程状态描述放在Thread类里面的枚举类State中.总共包含了6中状态,具体详情需要的小伙伴可以参考一下文章描述
    2022-07-07
  • Java编程获取当前屏幕分辨率的方法示例

    Java编程获取当前屏幕分辨率的方法示例

    这篇文章主要介绍了Java编程获取当前屏幕分辨率的方法,涉及java针对系统硬件信息的相关操作技巧,需要的朋友可以参考下
    2017-08-08
  • MyBatis-Plus如何解决主键自增问题

    MyBatis-Plus如何解决主键自增问题

    这篇文章主要介绍了MyBatis-Plus如何解决主键自增问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • Hibernate中的多表查询及抓取策略

    Hibernate中的多表查询及抓取策略

    本文主要介绍了Hibernate中的多表查询及抓取策略,具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02

最新评论