参考 : http://www.redis.cn/topics/notifications.html
redis key space 通知允许客户端订阅发布/订阅频道,以某种方式接收影响 Redis 数据集的事件。但是只能接收客户端连接期间发生的事件,如果客户端断开连接,并在稍后重连,那么所有在客户端断开期间发送的事件将会丢失。比较常用的是 key 过期通知。
介绍
默认情况下,键空间事件通知是不启用的,可以使用 redis.conf 中的 notify-keyspace-events 或者使用 CONFIG SET
命令来开启通知。
将参数设置为空字符串会禁用通知。 为了开启通知功能,使用了一个非空字符串,由多个字符组成,每一个字符都有其特殊的含义,具体参见下表:
K 键空间事件,以__keyspace@<db>__前缀发布。
E 键事件事件,以__keyevent@<db>__前缀发布。
g 通用命令(非类型特定),如DEL,EXPIRE,RENAME等等
$ 字符串命令
l 列表命令
s 集合命令
h 哈希命令
z 有序集合命令
x 过期事件(每次键到期时生成的事件)
e 被驱逐的事件(当一个键由于达到最大内存而被驱逐时产生的事件)
A g$lshzxe的别名,因此字符串AKE表示所有的事件。
字符串中应当至少存在K或者E,否则将不会传递事件,不管字符串中其余部分是什么。
例如,要为列表开启键空间事件,则配置参数必须设置为Kl,以此类推。字符串KEA可以用于开启所有可能的事件。
# 监听过期 key
redis-cli CONFIG SET notify-keyspace-events Ex
redis-cli --csv psubscribe '__key*__:*'
另一个终端 :
# SET key value EX second
SET key1 value1 EX 10
# 等 10 秒后,可以在前一个终端发现
root@dreamboy:~# redis-cli
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> CONFIG SET notify-keyspace-events Ex
OK
127.0.0.1:6379> psubscribe '__key*__:*'
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__key*__:*"
3) (integer) 1
1) "pmessage"
2) "__key*__:*"
3) "__keyevent@0__:expired"
4) "key1"
springboot 实现监听过期 key
测试之前需要保证 redis server 中的属性 notify-keyspace-events
中包含有 Ex
。
springboot 有实现 redis key 过期时间监听 : org.springframework.data.redis.listener.KeyExpirationEventMessageListener
, 那么只需要重写它的 onMessage
方法即可以自己监听过期 key。
pom.xml :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
config 配置 :
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.RedisMessageListenerContainer;
/**
* RedisListenerConfig
*
* @author xh
* @date 2020/5/9
*/
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
监听 key 过期 :
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
/**
* RedisKeyExpirationListener
*
* <p>
* redis 中需设置 : CONFIG SET notify-keyspace-events Ex
* </p>
*
* <p>
* 监听所有db的过期事件__keyevent@*__:expired"
* </p>
*
* @author xh
* @date 2020/5/9
*/
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* Callback for processing received objects through Redis.
*
* <p>
* 这里是针对 redis 数据失效事件
* </p>
*
* @param message message must not be {@literal null}.
* @param pattern pattern matching the channel (if specified) - can be {@literal null}.
*/
@Override
public void onMessage(Message message, byte[] pattern) {
// 获取到失效的 key,进行取消订单业务处理
String expiredKey = message.toString();
// new String(pattern) ==> __keyevent@*__:expired
System.out.println("expired key --> " + expiredKey);
}
}
使用 redis 发布订阅模式订阅 key 过期
测试之前需要保证 redis server 中的属性 notify-keyspace-events
中包含有 Ex
。
redis 事件通知是使用 Redis 普通发布订阅层传递,因此可以直接订阅 key 失效时间。
使用普通的 java 类接收消息 :
/**
* RedisReceiver
*
* @author xh
* @date 2020/5/9
*/
public class RedisKeyExpireReceiver {
public void receiveMessage(String message) {
System.out.println("receive message -> " + message);
}
}
把前面的 java 类注入 redis 配置 :
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;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
/**
* RedisListenerConfig2
*
* @author xh
* @date 2020/5/9
*/
@Configuration
public class RedisListenerConfig2 {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 如果只匹配 可以过期事件,那么正则表达式是 : __keyevent@*__:expired
// 如果匹配其他的,用对应的表达式就好
container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@*__:expired2"));
return container;
}
@Bean
MessageListenerAdapter listenerAdapter(RedisKeyExpireReceiver receiver) {
// 这里注入 bean 和 方法名
// redis 的默认方法名是 handleMessage
return new MessageListenerAdapter(receiver, "receiveMessage");
}
@Bean
RedisKeyExpireReceiver redisKeyExpireReceiver() {
return new RedisKeyExpireReceiver();
}
}