前言
前一阵子遇到了一个头疼两天的问题,如何在项目运行中动态自定义一个以小时为单位的定时任务?我后来想到了redis键空间,用来监听过期的key,结合存储的执行间隔,重新存储有效期的key。
正文
查了查大佬们的文章,简单学习后,了解了redis键空间的用法和基本的知识。
什么是redis键空间?
Redis的键空间通知:keyspace notifications
这个功能是在2.8版本后加入的,在客户端通过订阅鸡汁,来接收那些以某种方式改变了redis数据空间的事件通知。
比如说,改变key的命令;所有在0号库过期的key;
事件类型
改变redis数据空间的每个操作,键空间通知都会发送两个不同的事件。
比如在1号数据库,执行 del zhangsan 的操作,将会触发两个消息:
1、keyevent@1:del zhangsan
2、keyspace@1:zhangsan del
keyspace是对所有zhangsan的key操作进行监听的。
keyevent是对所有执行成功的del操作进行监听,
如果有订阅者订阅监听了他,则会收到zhangsan返回的通知。
我就是利用了可以接收某个库过期的key这个操作,完成了这个定时任务。
而这个功能是可以通过redis配置来实现的,下面来说下到底怎么修改这个配置。
redis配置
因为键空间通知会消耗一定的CPU时间,所以在默认情况下,redis是关闭这个功能的,我们需要手动修改redis.conf来修改此功能的开启状态。
找到配置中 # notify-keyspace-events去掉注释,然后重新启动redis就可以了,easy吧?
这条配置,是需要在后面加多个指定的字符组成,来代表其不同的功能:
K:keyspace事件,事件以__keyspace@<库名>__为前缀进行发布;
E:keyevent事件,事件以__keyevent@<库名>__为前缀进行发布;
g:一般性的,非特定类型的命令,比如del,expire,rename等;
$:字符串特定命令;
l:列表特定命令;
s:集合特定命令;
h:哈希特定命令;
z:有序集合特定命令;
x:过期事件,当某个键过期并删除时会产生该事件;
e:驱逐事件,当某个键因maxmemore策略而被删除时,产生该事件;
A:g$lshzxe的别名,因此”AKE”意味着所有事件。
这些选项中,至少要包含K或者E,否则就没啥用了
比如我在这个定时任务中,使用了E和x:notify-keyspace-events Ex
话不多说了,上菜!
@Configuration
public class RedisConfiguration {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean(name = "redisTemplate")
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
/**
* 监听key过期事件
*/
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
return container;
}
/**
* 指定监听库
*/
@Bean
public ChannelTopic expiredTopic() {
return new ChannelTopic("__keyevent@1__:expired");
}
}
expiredTopic()方法,就可以指定某个库,某个事件,expired就是过期啦。
@Component
@Slf4j
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* 失效后回调函数
* @param message
* @param pattern
*/
@SneakyThrows
@Override
public void onMessage(Message message, byte[] pattern) {
super.onMessage(message, pattern);
String msg = new String(message.getBody(), "UTF-8");
String channel = new String(message.getChannel(), "UTF-8");
log.info("Redis-Listener Channel:"+channel+" Listen to the key:"+msg);
//msg字符串处理,获取想要的key名字....
//具体的业务处理
//想要定时,那就继续把key存到redis,结合过期时间
}
}
正菜就是这些,具体业务还是需要结合不同项目,这里就不单独写我的了,至于怎么重新存储redis,这就不再多说了,工具类一找一大堆的哇!
总结
测试类我就不写出来了,自己动动手,马上就明白了这个redis键空间的用法。
可能这个功能会再以后运行中出现一些问题,在这里提几个,提前有所准备吧!比如说:
1、redis挂了?
2、一般来说这种动态定时不是很多,可如果很多,占内存或者影响速度怎么办?
3、反复启停定时,是否影响redis的其它操作?如果影响了,怎么办?
4、redis的订阅发布模式是fire and forget模式,发了就扔了,所以说收不到咋办,定时不存啦?
5、如果订阅发布的客户端over了又重连,岂不是事件都丢了?
好啦,大概提几个可能遇到的,我就不单独去说了,毕竟我也还没遇到咧!我这个项目比较小,问题也不大。但真的也要留心,任何一个错误,都不是小事!
希望对大家有点帮助!我是左小涩,一个独自在大城市努力的年轻人。