很多时候我们想把事件在某些事件之后执行
如:提交失败的数据每隔一些时间段执行一次(1min,30min,2h)
如:订单下单没有付款,第1天,第3天发短信提示
一、这就需要延迟调度实现,本文介绍两种方式
1、使用消息中间件rabbitmq延迟死信队列实现
rabbitmq方式类图展示

package cn.quantgroup.cashloanflow.config.mq;
import cn.quantgroup.clf.api.delayretry.service.consumer.ConsumeDelayEventService;
import cn.quantgroup.tech.brave.service.ITechRabbitBuilder;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerEndpoint;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @author: amen
* @date: 2019-01-08
*/
@Configuration
public class DelayMqConfig {
@Value("${ka.rabbitmq.connection.host}")
private String host;
@Value("${ka.rabbitmq.connection.port}")
private Integer port;
@Value("${ka.rabbitmq.connection.user}")
private String user;
@Value("${ka.rabbitmq.connection.password}")
private String password;
@Value("${ka.rabbitmq.connection.virtual-host}")
private String kaLoanVirtualHost;
@Value("${ka.rabbitmq.delay.queue:ka.delay.queue}")
private String delayQueue;
@Value("${ka.rabbitmq.delay.exchange:ka.delay.exchange}")
private String delayExchange;
@Value("${ka.rabbitmq.delay.routingKey:ka.delay.routingKey}")
private String delayRoutingKey;
@Value("${ka.rabbitmq.dlx.queue:ka.dlx.queue}")
private String dlxQueue;
@Value("${ka.rabbitmq.dlx.exchange:ka.dlx.exchange}")
private String dlxExchange;
@Value("${ka.rabbitmq.dlx.routingKey:ka.dlx.routingKey}")
private String dlxRoutingKey;
@Autowired
@Qualifier( "techRabbitBuilder" )
private ITechRabbitBuilder techRabbitBuilder;
@Bean("delayCachingConnectionFactory")
public CachingConnectionFactory delayCachingConnectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host, port);
connectionFactory.setCacheMode(CachingConnectionFactory.CacheMode.CONNECTION);
connectionFactory.setChannelCacheSize(1024);
connectionFactory.setConnectionCacheSize(1024);
connectionFactory.setUsername(user);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(kaLoanVirtualHost);
connectionFactory.setPublisherReturns(false);
connectionFactory.setPublisherConfirms(false);
return connectionFactory;
}
/**
* 申明死信队列
* @return
*/
@Bean("dlxExchangeBean")
public DirectExchange dlxExchangeBean() {
return new DirectExchange(dlxExchange);
}
@Bean("dlxQueueBean")
public Queue dlxQueueBean() {
return new Queue(dlxQueue);
}
@Bean
public Binding binding(@Qualifier("dlxExchangeBean") DirectExchange dlxExchange, @Qualifier("dlxQueueBean") Queue dlxQueue) {
return BindingBuilder.bind(dlxQueue).to(dlxExchange).with(dlxRoutingKey);
}
/**
* 申明延时队列
* @return
*/
@Bean("delayExchange")
public DirectExchange delayExchangeBean() {
return new DirectExchange(delayExchange);
}
@Bean("delayQueue")
public Queue orderQueueBean() {
Map<String, Object> arguments = new HashMap<>(4);
// 绑定该队列到死信交换机
arguments.put("x-dead-letter-exchange", dlxExchange);
arguments.put("x-dead-letter-routing-key", dlxRoutingKey);
return new Queue(delayQueue, true, false, false, arguments);
}
@Bean
public Binding orderBinding(@Qualifier("delayExchange") DirectExchange delayExchange, @Qualifier("delayQueue") Queue delayQueue) {
return BindingBuilder.bind(delayQueue).to(delayExchange).with(delayRoutingKey);
}
/**
* 监听死信队列
* @param consumeDelayEventService
* @param cachingConnectionFactory
* @param dlxQueue
* @return
*/
@Bean("delaySimpleMessageListenerContainer")
public SimpleMessageListenerContainer delaySimpleMessageListenerContainer(@Qualifier("consumeMqDelayEventService") ConsumeDelayEventService consumeDelayEventService,
@Qualifier("delayCachingConnectionFactory") CachingConnectionFactory cachingConnectionFactory,
@Qualifier("dlxQueueBean") Queue dlxQueue) {
SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory = techRabbitBuilder
.createSimpleRabbitListenerContainerFactory(cachingConnectionFactory);
SimpleRabbitListenerEndpoint simpleRabbitListenerEndpoint = new SimpleRabbitListenerEndpoint();
simpleRabbitListenerEndpoint.setQueues(dlxQueue);
simpleRabbitListenerEndpoint.setMessageListener(new MessageListenerAdapter(consumeDelayEventService, "consumeMessage"));
SimpleMessageListenerContainer container = simpleRabbitListenerContainerFactory.createListenerContainer(simpleRabbitListenerEndpoint);
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
container.setConcurrentConsumers(10);
container.start();
return container;
}
@Bean(name = "delayRabbitTemplate")
public RabbitTemplate delayRabbitTemplate(@Qualifier("delayCachingConnectionFactory") ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = techRabbitBuilder.createRabbitTemplate(connectionFactory);
rabbitTemplate.setExchange(delayExchange);
rabbitTemplate.setRoutingKey(delayRoutingKey);
return rabbitTemplate;
}
}
@Resource(name = "delayRabbitTemplate")
private RabbitTemplate rabbitTemplate;
@Override
public void sendDelayEvent(DelayEventBody delayEventBody) {
Long delayMillis = DelayRetryUtil.getDelayMillis(delayEventBody);
rabbitTemplate.convertAndSend(JSONTools.serialize(delayEventBody), message -> {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
message.getMessageProperties().setExpiration(delayMillis + "");
return message;
});
final Date executeTime = new Date(System.currentTimeMillis() + delayMillis);
log.info("[delayMessageSend][RabbitMqDelay]延迟消息已发送, 延迟时间为:{},执行时间为:{},消息体为:{}",
JSONTools.serialize(DelayRetryUtil.getDelayTimeModel(delayEventBody)),
DateUtil.format(executeTime, DateUtil.DATE_FORMAT_1), JSONTools.serialize(delayEventBody));
}
2、使用redis.zset存储关键key,使用执行时间作为score,每次取set首位元素比较当前时间(需要定时调度任务辅助) redis方式类图展示

二、这种设计支持分布式,也支持高可用
不管对于mq还是redis,一般同一个服务不会是一台宿主机,多个宿主机节点。mq多个消费者监听同一个队列,由mq管理有效消费者。多个服务当定时任务触发的机器去请求redis,这种情况最好定时任务是支持分布式的.可以结合当当网elastic job(https://blog.youkuaiyun.com/styhm/article/details/81034729)