当前位置 博文首页 > 抱明月而长终:【Redis系列】Spring boot实现监听Redis key失效

    抱明月而长终:【Redis系列】Spring boot实现监听Redis key失效

    作者:抱明月而长终 时间:2021-01-26 00:58

    talk is cheap, show me the code.

    一、开启Redis key过期提醒

    • 方式二:修改配置文件

      redis.conf

      # 默认 notify-keyspace-events ""
      notify-keyspace-events Ex
      
    • 方式二:命令行开启

      CONFIG SET notify-keyspace-events Ex
      CONFIG GET notify-keyspace-events
      

    二、notify-keyspace-events

    notify-keyspace-events 选项的默认值为空

    notify-keyspace-events 的参数可以是以下字符的任意组合, 它指定了服务器该发送哪些类型的通知。

    字符 发送的通知
    K 键空间通知,所有通知以 keyspace@ 为前缀
    E 键事件通知,所有通知以 keyevent@ 为前缀
    g DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知
    $ 字符串命令的通知
    l 列表命令的通知
    s 集合命令的通知
    h 哈希命令的通知
    z 有序集合命令的通知
    x 过期事件:每当有过期键被删除时发送
    e 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送
    A 参数 g$lshzxe 的别名

    三、Coding

    1. 初始化一个Spring Boot项目

    2. pom.xml

      <dependencies>
      		<dependency>
      			<groupId>org.springframework.boot</groupId>
      			<artifactId>spring-boot-starter-data-redis</artifactId>
      		</dependency>
      		<dependency>
      			<groupId>org.projectlombok</groupId>
      			<artifactId>lombok</artifactId>
      		</dependency>
      	</dependencies>
      
    3. 定义配置类RedisListenerConfig

      @Configuration
      public class RedisListenerConfig {
      
      	@Bean
      	RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
      		RedisMessageListenerContainer container = new RedisMessageListenerContainer();
      		container.setConnectionFactory(connectionFactory);
      		return container;
      	}
      
      }
      
    4. 定义数据生产类ProviderDataToRedis

      @Slf4j
      @Component
      public class ProviderDataToRedis implements CommandLineRunner {
      
      	@Autowired
      	private StringRedisTemplate stringRedisTemplate;
      
      	@Override
      	public void run(String... args) throws Exception {
      		int[] num = new int[]{1};
      		Random random = new Random();
      		while (true) {
      			int max = random.nextInt(5);
      			IntStream.range(0, max).forEach(n -> stringRedisTemplate.opsForValue().set(String.format("mq:s1:%s", ++num[0]), "已预订", 5, TimeUnit.SECONDS));
      			log.info("放了 {} 条数据到redis...", max);
      			TimeUnit.SECONDS.sleep(3);
      		}
      	}
      }
      
    5. 定义监听器 实现KeyExpirationEventMessageListener接口

      查看源码发现,该接口监听所有db的过期事件keyevent@*:expired"

      定义Status1ExpirationListener监听状态1到期

      @Slf4j
      @Component
      public class Status1ExpirationListener extends KeyExpirationEventMessageListener {
      
      	public Status1ExpirationListener(RedisMessageListenerContainer listenerContainer) {
      		super(listenerContainer);
      	}
      
      	@Autowired
      	private StringRedisTemplate stringRedisTemplate;
      
      	@Override
      	public void onMessage(Message message, byte[] pattern) {
      		// 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
      		String expiredKey = message.toString();
      		if (expiredKey.startsWith("mq:s1:")) {
      			log.info("-----------------------------------");
      			log.info(String.format("过期key[%s]", expiredKey));
      			String newKey = String.format("mq:s2:%s", expiredKey.substring(6));
      			String newValue = "行程中";
      			stringRedisTemplate.opsForValue().set(newKey, newValue, 3, TimeUnit.SECONDS);
      			log.info(String.format("%s: %s", newKey, newValue));
      			log.info("-----------------------------------");
      		}
      	}
      
      }
      

      定义Status2ExpirationListener监听状态2到期

      @Slf4j
      @Component
      public class Status2ExpirationListener extends KeyExpirationEventMessageListener {
      
      	public Status2ExpirationListener(RedisMessageListenerContainer listenerContainer) {
      		super(listenerContainer);
      	}
      
      	@Override
      	public void onMessage(Message message, byte[] pattern) {
      		// 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
      		String expiredKey = message.toString();
      		if (expiredKey.startsWith("mq:s2:")) {
      			log.info("***********************************");
      			log.info(String.format("过期key[%s]", expiredKey));
      			log.info("[{}]行程已完成,修改数据库状态。", newKey);
      			log.info("***********************************");
      		}
      	}
      
      }
      

    四、测试输出

    ...
    2021-01-25 23:16:58.012  INFO 55511 --- [           main] n.y.tools.listener.ProviderDataToRedis      : 放了 4 条数据到redis...
    2021-01-25 23:17:00.037  INFO 55511 --- [ container-1070] c.i.r.l.Status1ExpirationListener        : -----------------------------------
    2021-01-25 23:17:00.037  INFO 55511 --- [ container-1070] c.i.r.l.Status1ExpirationListener        : 过期key[mq:s1:272]
    2021-01-25 23:17:00.037  INFO 55511 --- [ container-1072] c.i.r.l.Status1ExpirationListener        : -----------------------------------
    2021-01-25 23:17:00.037  INFO 55511 --- [ container-1072] c.i.r.l.Status1ExpirationListener        : 过期key[mq:s1:271]
    2021-01-25 23:17:00.039  INFO 55511 --- [ container-1070] c.i.r.l.Status1ExpirationListener        : mq:s2:272: 行程中
    2021-01-25 23:17:00.039  INFO 55511 --- [ container-1072] c.i.r.l.Status1ExpirationListener        : mq:s2:271: 行程中
    2021-01-25 23:17:00.039  INFO 55511 --- [ container-1070] c.i.r.l.Status1ExpirationListener        : -----------------------------------
    2021-01-25 23:17:00.039  INFO 55511 --- [ container-1072] c.i.r.l.Status1ExpirationListener        : -----------------------------------
    2021-01-25 23:17:00.140  INFO 55511 --- [ container-1075] c.i.r.l.Status2ExpirationListener        : ***********************************
    2021-01-25 23:17:00.140  INFO 55511 --- [ container-1075] c.i.r.l.Status2ExpirationListener        : 过期key[mq:s2:270]
    2021-01-25 23:17:00.140  INFO 55511 --- [ container-1075] c.i.r.l.Status2ExpirationListener        : [270]行程已完成,修改数据库状态。
    2021-01-25 23:17:00.140  INFO 55511 --- [ container-1075] c.i.r.l.Status2ExpirationListener        : ***********************************
    2021-01-25 23:17:00.242  INFO 55511 --- [ container-1077] c.i.r.l.Status2ExpirationListener        : ***********************************
    2021-01-25 23:17:00.242  INFO 55511 --- [ container-1077] c.i.r.l.Status2ExpirationListener        : 过期key[mq:s2:269]
    2021-01-25 23:17:00.242  INFO 55511 --- [ container-1079] c.i.r.l.Status2ExpirationListener        : ***********************************
    2021-01-25 23:17:00.242  INFO 55511 --- [ container-1079] c.i.r.l.Status2ExpirationListener        : 过期key[mq:s2:268]
    2021-01-25 23:17:00.242  INFO 55511 --- [ container-1077] c.i.r.l.Status2ExpirationListener        : [269]行程已完成,修改数据库状态。
    2021-01-25 23:17:00.242  INFO 55511 --- [ container-1077] c.i.r.l.Status2ExpirationListener        : ***********************************
    2021-01-25 23:17:00.242  INFO 55511 --- [ container-1079] c.i.r.l.Status2ExpirationListener        : [268]行程已完成,修改数据库状态。
    2021-01-25 23:17:00.242  INFO 55511 --- [ container-1079] c.i.r.l.Status2ExpirationListener        : ***********************************
    2021-01-25 23:17:00.546  INFO 55511 --- [ container-1081] c.i.r.l.Status2ExpirationListener        : ***********************************
    2021-01-25 23:17:00.546  INFO 55511 --- [ container-1081] c.i.r.l.Status2ExpirationListener        : 过期key[mq:s2:267]
    2021-01-25 23:17:00.546  INFO 55511 --- [ container-1081] c.i.r.l.Status2ExpirationListener        : [267]行程已完成,修改数据库状态。
    2021-01-25 23:17:00.546  INFO 55511 --- [ container-1081] c.i.r.l.Status2ExpirationListener        : ***********************************
    ...
    

    五、一直增加的线程数

    从测试输出的日志中可以看出,线程一直在增加,这个问题还有待解决!

    六、参考

    http://redisdoc.com/topic/notification.html

    下一篇:没有了