目录
3、@EnableScheduling、@Scheduled
6、Redis客户端Redission中RDelayedQueue
1、DelayQueue
适用于单机的应用,不支持分布式的延迟消息,并且延迟消息不支持持久化,对可靠性要求不是很高的场景可以使用这种方式。
2、Timer、TimerTask(不推荐)
参考文档: Timer和TimerTask 示例 、Timer管理延时任务的缺陷
缺陷:
- 不提倡使用Timer这个线程工具,因为Timer在执行TimerTask时,如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行,这是它的一个坑。
- Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化会出现一些执行上的变化,ScheduledExecutorService基于时间的延迟,不会由于系统时间的改变发生执行变化。
- 调度线程与执行线程都是Timer,如果某个任务执行比较耗时,则会出现任务真正执行的时间延迟。
Timer&TimerTask已经被Oracle放弃了,deprecate Timer and TimerTask
3、ScheduledExcecutorService
ScheduledThreadPool内部是一个线程池正好克服了Timer中任务单线程执行时的问题3,并且克服了问题1与2,使用ScheduledThreadPool比Timer要好。
public interface ScheduledExecutorService extends ExecutorService {
/**
* Creates and executes a one-shot action that becomes enabled
* after the given delay.
*
* @param command the task to execute
* @param delay the time from now to delay execution
* @param unit the time unit of the delay parameter
* @return a ScheduledFuture representing pending completion of
* the task and whose {@code get()} method will return
* {@code null} upon completion
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if command is null
*/
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
}
3、@EnableScheduling、@Scheduled
参考文档: SpringBoot使用@Scheduled创建定时任务
底层还是通过ScheduledThreadPool来实现的,只是通过声明注解式的方式进行使用。
4、Netty 工具类 HashedWheelTimer
参考文档: 时间轮HashedWheelTimer原理
任务的执行都是通过worker单线程执行,如过时间格子长度设置过小而任务执行时间较长,则会导致执行的时间不精确。因此可以将任务异步的执行可以加速任务的遍历。
5、有赞基于Redis的ZSet实现
参考文档:有赞延迟队列设计
设计不足的地方
timer是通过独立线程的无限循环来实现,在没有ready job的时候会对CPU造成一定的浪费。
消费端在reserve job的时候,采用的是http短轮询的方式,且每次只能取的一个job。如果ready job较多的时候会加大网络I/O的消耗。
数据存储使用的redis,消息在持久化上受限于redis的特性。
scale-out的时候依赖第三方(nginx)。
6、Redis客户端Redission中RDelayedQueue
参考文档: 7.15. 延迟队列(Delayed Queue)
基于Redis的Redisson分布式延迟队列(Delayed Queue)结构的RDelayedQueue
Java对象在实现了RQueue
接口的基础上提供了向队列按要求延迟添加项目的功能。该功能可以用来实现消息传送延迟按几何增长或几何衰减的发送策略。
RQueue<String> distinationQueue = ...
RDelayedQueue<String> delayedQueue = getDelayedQueue(distinationQueue);
// 10秒钟以后将消息发送到指定队列
delayedQueue.offer("msg1", 10, TimeUnit.SECONDS);
// 一分钟以后将消息发送到指定队列
delayedQueue.offer("msg2", 1, TimeUnit.MINUTES);
在该对象不再需要的情况下,应该主动销毁。仅在相关的Redisson对象也需要关闭的时候可以不用主动销毁。
RDelayedQueue<String> delayedQueue = ...
delayedQueue.destroy();
7、基于Redis自定义实现
参考文档: Redis实现延迟队列 (源代码)
8、xxl-job 通过定时任务来实现延迟任务
参考文档: xxl-job 通过定时任务来实现延迟任务
9、ActiveMQ
一般的MQ中间件都支持延迟消息,ActiveMQ的配置如下案例。
首先,让activemq支持调度消息,默认是不支持的,编辑 activemq.xml 配置文件,如下配置:
然后,客户端代码如下:
// step 6:发送MQ:延迟消息,用于处理appending状态的数据,自动失败,防止长时间que-service不进行响应,超时失败
try {
jmsTemplate.send(MqConfig.HOMEWORK_CLASS_WORONG_QUE_CUSTOM_CREATE_INFO_AUTO_FAIL, session -> {
MapMessage mapMessage = session.createMapMessage();
mapMessage.setLong("createInfoId", createInfoId);
Message msssage = jmsTemplate.getMessageConverter().toMessage(mapMessage, session);
ScheduleMessagePostProcessor postProcessor = new ScheduleMessagePostProcessor(3, TimeUnit.HOURS);
log.info("异步调用que-service,发送MQ延迟消息,超时自动失败,createInfoId:{}", createInfoId);
return postProcessor.postProcessMessage(msssage);
});
} catch (Exception e) {
LoggerUtil.error(log, e, "异步调用que-service,发送MQ延迟消息,超时自动失败,异常上下文,createInfoId:{}", createInfoId);
}
ScheduleMessagePostProcessor 类代码如下:
package com.isatk.yn.module.common.misc;
import java.util.concurrent.TimeUnit;
import javax.jms.JMSException;
import javax.jms.Message;
import org.apache.activemq.ScheduledMessage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jms.core.MessagePostProcessor;
import lombok.Data;
@Data
public class ScheduleMessagePostProcessor implements MessagePostProcessor {
private long delayInMillis = 0L;
private String cron = null;
public ScheduleMessagePostProcessor(long delay, TimeUnit timeUnit) {
this.delayInMillis = timeUnit.toMillis(delay);
}
public ScheduleMessagePostProcessor(String cron) {
this.cron = cron;
}
@Override
public Message postProcessMessage(Message message) throws JMSException {
if (delayInMillis > 0) {
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delayInMillis);
}
if (StringUtils.isNotEmpty(cron)) {
message.setStringProperty(ScheduledMessage.AMQ_SCHEDULED_CRON, cron);
}
return message;
}
}