实现超时订单/超期默认好评等等很多常见需要用到延时任务,大概用几种做法:
- redis key失效监听
- rabbitmq死信队列
- 调度线程池
- 定时调度
一. redis key失效监听实现(推荐),注意redis.conf 放开 notify-keyspace-events Ex
/**
*取消订单,超期默认好评等业务,考试到期自动交卷结束等
*/
@Configuration
@Slf4j
public class RedisKeyExpireListenerConfig {
@Value("${spring.redis.database:0}")
private String redisDatabase;
@Autowired
private ExamMapper examMapper;
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory factory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
container.addMessageListener((message, pattern) -> handleKeyExpire(message), new PatternTopic("__keyevent@"+redisDatabase+"__:expired")); //监听16库的key失效
return container;
}
private void handleKeyExpire(Message message){
String redisKey = new String(message.getBody());
//监听到有考试结束,修改考试状态及结束时间
if (redisKey.startsWith(RedisConstant.EXAM_TIMEOUT)){
String examId = redisKey.replace(RedisConstant.EXAM_TIMEOUT, "");
int i = examMapper.examOver(examId);
log.warn("考试超时自动提交试卷ID:{},结果:{}",examId,i);
}
}
}
二. rabbitmq死信队列
死信队列是rabbitmq有一个特殊属性的普通队列,spring 配置类配置好普通队列,死信队列及与各自的交换机绑定,并且普通队列配置与死信队列的绑定。业务基本流程:参考
- 业务消息被投入业务队列
- 消费者消费业务队列的消息,由于处理过程中发生异常,于是进行了nck或者reject操作(须配置手动ack)
- 被nck或reject的消息/超时消息由RabbitMQ投递到死信交换机中
- 死信交换机将消息投入相应的死信队列
- 死信队列的消费者消费死信消息
注意普通队列的生产者发送消息时设置超时时间: rabbitTemplate.convertAndSend("EXCHANGE", "KEY", p, 该参数设置TTL, data);
三.定时任务
如果要求延迟低而设置高频定时任务可能会导致性能问题;或频率一般,但操作的DB 表比较大也可能导致性能问题。是否通过定时任务解决需要时具体情况而定
四.调度线程池
实现最简单,但遇到宕机或重启可能丢失部分任务
@Autowire
private ScheduledThreadPoolExecutor executor;
...
Future future = executor.schedule(() -> {
System.out.println("具体业务");
}, 3L, TimeUnit.SECONDS);