动态定时任务
项目基于Spring Boot 2.3.x。
项目需求有一个功能,可以添加多个定时触发一次的任务,并且这个触发时间在运行过程中(触发前)是更改的,于是想到使用动态定时任务。
目前想到的方法:
- 任务写入数据库,定时轮询
- 定时轮询,消耗资源,并且实时性不强
- 使用RabbitMQ的TTL队列
- 消息推入队列后只能等待消费,无法更改,如果修改了任务的触发时间,需要推入一条新消息,并且旧消息依旧存在
- Redis keyspace notifications
Redis keyspace notifications
原理:使用Redis中的Redis keyspace notifications,去订阅一个Key过期的事件,在更改任务触发事件时,只需更改Key的过期时间。
spring-boot-starter-data-redis中有对应的实现。
- 配置
RedisConfig
@Configuration public class RedisConfig{ // 其他配置 /** * Reids消息监听器容器 */ @Bean public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory); return redisMessageListenerContainer; } }
- 创建事件监听器
@Component public class RedisKeyExpirationEventMessageListener extends KeyExpirationEventMessageListener { /** * @Component修饰的Bean使用构造函数构造对象时会自动注入构造函数的参数 * * @param listenerContainer */ public RedisKeyExpirationEventMessageListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } /** * 消息过期时处理 * message只会返回Key,而不会返回值 * 根据业务可以在设置时拼装Key,在处理时进行拆分 * * @param message 过期消息 */ @Override protected void doHandleMessage(Message message) { String key = message.toString(); // TODO } }
点进KeyspaceEventMessageListener
,可以看到其继承了KeyspaceEventMessageListener
,而KeyspaceEventMessageListener
有一个需要重写的方法doHandleMessage(Message message)
,以及一个注册方法doRegister(RedisMessageListenerContainer container)
,在注册方法中向Reids消息监听器容器注册了当前的消息监听器。
public abstract class KeyspaceEventMessageListener implements MessageListener, InitializingBean, DisposableBean {
// 订阅的主题,代表订阅所有的keyevent
private static final Topic TOPIC_ALL_KEYEVENTS = new PatternTopic("__keyevent@*");
/**
* Register instance within the container.
* 向容器注册当前实例
*
* @param container never {@literal null}.
*/
protected void doRegister(RedisMessageListenerContainer container) {
listenerContainer.addMessageListener(this, TOPIC_ALL_KEYEVENTS);
}
}
那我们就可以发散一下,根据Redis的官方文档,自定义一些其他的事件监听。
/**
* Key删除事件
*/
@Component
public class RedisKeyDeleteEventMessageListener extends KeyspaceEventMessageListener {
private static final Topic TOPIC = new PatternTopic("__keyevent@*__:del");
public RedisKeyDeleteEventMessageListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
protected void doRegister(RedisMessageListenerContainer listenerContainer) {
listenerContainer.addMessageListener(this, TOPIC);
}
@Override
protected void doHandleMessage(Message message) {
System.out.println(message + "删除了");
}
}