SpringBoot監聽Redis key失效事件


SpringBoot監聽Redis key失效事件

一、問題背景

設備發送的心跳數據中的狀態信息會保存在Redis緩存中,當緩存中的key超時失效時,將根據key中的設備id更新數據庫中的數據,這時就需要監聽Redis 的key失效事件

二、解決方案

1.開啟Redis key的過期提醒

修改Redis的配置文件redis.conf,找到配置(沒有就新增)notify-keyspace-event

默認為:notify-keyspace-event ""
修改為:notify-keyspace-event Ex

相關參數說明

K:keyspace事件,事件以__keyspace@<db>__為前綴進行發布;        
E:keyevent事件,事件以__keyevent@<db>__為前綴進行發布;        
g:一般性的,非特定類型的命令,比如del,expire,rename等;       
$:字符串特定命令;        
l:列表特定命令;        
s:集合特定命令;        
h:哈希特定命令;        
z:有序集合特定命令;        
x:過期事件,當某個鍵過期並刪除時會產生該事件;        
e:驅逐事件,當某個鍵因maxmemore策略而被刪除時,產生該事件;        
A:g$lshzxe的別名,因此”AKE”意味着所有事件。

2.使用Redis客戶端測試

打開redis-cli客戶端,監控db為0的key過期事件

config set notify-keyspace-events Ex
# __keyevent@<db>__ db為Redis的第幾個庫,默認為0
PSUBSCRIBE __keyevent@0__:expired

執行命令
另打開一個客戶端redis-cli,發送定時過期key

setex hello 2 world

在這里插入圖片描述
觀察上一個客戶端,會發現接收到了過期key hello,但是無法收到hello的value
在這里插入圖片描述

3.在SpringBoot中使用該特性

(1)在pom中添加Redis的依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)定義Redis的監聽配置RedisListenerConfig

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

/**
 * <p>Title: RedisListenerConfig</p>
 * <p>Description: </p>
 *
 * @author Jeff
 * @version V1.0
 * @date 2021/5/10 10:26
 * <p>Copyright: Copyright (c) 2020 版權</p>
 */
@Configuration
public class RedisListenerConfig {
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 事件以__keyevent@<db>__為前綴進行發布
        container.addMessageListener(new RedisKeyExpirationListener(container), new PatternTopic("__keyevent@0__" +
                ":expired"));
        return container;
    }
}

(3)定義監聽器

實現KeyExpirationEventMessageListener接口,該接口監聽所有db的過期時間keyevent@*:expired

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;

/**
 * <p>Title: RedisKeyExpirationListener</p>
 * <p>Description: </p>
 * 該監聽器監聽的是所有庫的key事件
 * keyevent@*:expired
 * @author Jeff
 * @version V1.0
 * @date 2021/5/10 10:27
 * <p>Copyright: Copyright (c) 2020 版權</p>
 */
@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 用戶做自己的業務處理即可,注意message.toString()可以獲取失效的key
        String expiredKey = message.toString();
        if (expiredKey.startsWith("device:heartbeat:")) {
            //TODO 獲取到需要處理的key,進行相關的業務處理
            // 這里只能拿到的是key,不能拿到key對應的value
        }
    }
}

該監聽器若要使用@Autowired注入會出現注入為空的問題,解決辦法參考
SpringBoot中@Component注解無法使用@Autowired注解的問題

優化

若我們繼承默認的KeyExpirationEventMessageListener,是無法動態的修改Redis的不同庫的
我們可以照着KeyExpirationEventMessageListener寫一個監聽器,比如寫一個監聽數據庫為6且監聽刪除操作的監聽器

只需要修改Topic 的值即可

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.core.RedisKeyExpiredEvent;
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.lang.Nullable;
/**
 * <p>Title: KeyDeleteEventMessageListener </p>
 * <p>Description: </p>
 * 監聽動態庫的刪除事件監聽器
 * keyevent@*:expired
 * @author Jeff
 * @version V1.0
 * @date 2021/5/10 10:27
 * <p>Copyright: Copyright (c) 2020 版權</p>
 */
public class KeyDeleteEventMessageListener extends KeyspaceEventMessageListener implements ApplicationEventPublisherAware {
	@Value("${spring.redis.database}")
	private String database;
	
    @Nullable
    private ApplicationEventPublisher publisher;

    public KeyDeleteEventMessageListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    protected void doRegister(RedisMessageListenerContainer listenerContainer) {
        listenerContainer.addMessageListener(this, new PatternTopic("__keyevent@" + database + "__:del"));
    }

    protected void doHandleMessage(Message message) {
        this.publishEvent(new RedisKeyExpiredEvent(message.getBody()));
    }

    protected void publishEvent(RedisKeyExpiredEvent event) {
        if (this.publisher != null) {
            this.publisher.publishEvent(event);
        }

    }

    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}

然后再RedisKeyExpirationListener繼承這個listener

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;

/**
 * <p>Title: RedisKeyExpirationListener</p>
 * <p>Description: </p>
 * 該監聽器監聽的是所有庫的key事件
 * keyevent@*:expired
 * @author Jeff
 * @version V1.0
 * @date 2021/5/10 10:27
 * <p>Copyright: Copyright (c) 2020 版權</p>
 */
@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyDeleteEventMessageListener {

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 用戶做自己的業務處理即可,注意message.toString()可以獲取失效的key
        String expiredKey = message.toString();
        if (expiredKey.startsWith("device:heartbeat:")) {
            //TODO 獲取到需要處理的key,進行相關的業務處理
            // 這里只能拿到的是key,不能拿到key對應的value
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM