几种实现延时任务的方式
一、应用场景
在需求开发过程中,我们经常会遇到一些类似下面的场景:
1)外卖订单超过15分钟未支付,自动取消
2)使用抢票软件订到车票后,1小时内未支付,自动取消
3)待处理申请超时1天,通知审核人员经理,超时2天通知审核人员总监
4)客户预定自如房子后,24小时内未支付,房源自动释放
那么针对这类场景的需求应该如果实现呢,我们最先想到的一般是启个定时任务,来扫描数据库里符合条件的数据,并对其进行更新操作。一般来说spring-quartz 、elasticjob 就可以实现,甚至自己写个 Timer 也可以。但是这种方式有个弊端,就是需要不停的扫描数据库,如果数据量比较大,并且任务执行间隔时间比较短,对数据库会有一定的压力。另外定时任务的执行间隔时间的粒度也不太好设置,设置长会影响时效性,设置太短又会增加服务压力。我们来看一下有没有更好的实现方式
package io.renren.common.wheeltimer;
import lombok.Data;
import java.text.SimpleDateFormat;
import java.util.Date;
@Data
public class Employer {
private String name;
private int age;
private String wife;
private Double salary;
private String putTime;
public void setPutTime() {
this.putTime = new SimpleDateFormat("hh:mm:ss").format(new Date());
}
}
package io.renren.common.wheeltimer;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* https://xie.infoq.cn/article/857cea9c7e0a05a483a8d5c96
*
* 一、应用场景
* 在需求开发过程中,我们经常会遇到一些类似下面的场景:
* 1)外卖订单超过15分钟未支付,自动取消
* 2)使用抢票软件订到车票后,1小时内未支付,自动取消
* 3)待处理申请超时1天,通知审核人员经理,超时2天通知审核人员总监
* 4)客户预定自如房子后,24小时内未支付,房源自动释放
*
* 那么针对这类场景的需求应该如果实现呢,我们最先想到的一般是启个定时任务,
* 来扫描数据库里符合条件的数据,并对其进行更新操作。一般来说spring-quartz 、elasticjob 就可以实现,
* 甚至自己写个 Timer 也可以。但是这种方式有个弊端,就是需要不停的扫描数据库,如果数据量比较大,并且任务执行间隔时间比较短,
* 对数据库会有一定的压力。另外定时任务的执行间隔时间的粒度也不太好设置,设置长会影响时效性,设置太短又会增加服务压力。
* 我们来看一下有没有更好的实现方式。
*/
@RestController
@RequestMapping("/app")
public class RedisPutInQueue {
@Autowired
RedissonClient redissonClient;
@Autowired
TaskProducer taskProducer;
@Autowired
TaskConsumer taskConsumer;
@GetMapping("/redisPutInQueue")
public void RedisPutInQueue(){
RBlockingQueue<Employer> blockingFairQueue = redissonClient.getBlockingQueue("delay_queue");
RDelayedQueue<Employer> delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue);
for (int i=0;i<10;i++){
try {
//模拟间隔投递消息
Thread.sleep(1*1000);
} catch (Exception e) {
e.printStackTrace();
}
//一分钟以后将消息发送到指定队列
//延迟队列包含callCdr 1分钟,然后将其传输到blockingFairQueue中
//在1分钟后就可以在blockingFairQueue 中获取callCdr了
Employer callCdr = new Employer();
callCdr.setSalary(345.6);
callCdr.setPutTime();
delayedQueue.offer(callCdr,1, TimeUnit.MILLISECONDS);
System.out.println("callCdr ==================> " + callCdr);
}
}
@GetMapping("/RedisGetOutQueue")
public void RedisGetOutQueue(){
//订单取消时间:03:01:28==订单生成时间03:01:28
RBlockingQueue<Employer> blockingFairQueue = redissonClient.getBlockingQueue("delay_queue");
RDelayedQueue<Employer> delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue);
while (true) {
Employer callCdr = null;
try {
callCdr = blockingFairQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单取消时间:" + new SimpleDateFormat("hh:mm:ss").format(new Date()) + "==订单生成时间" + callCdr.getPutTime());
}
}
@GetMapping("/redisPut")
public void redisPut(@RequestParam("a") Long a, @RequestParam("b")Long b, @RequestParam("c")Long c){
//创建 3个任务,并设置超时间为 10s 5s 20s
taskProducer.produce(1, System.currentTimeMillis() + a);
taskProducer.produce(2, System.currentTimeMillis() + b);
taskProducer.produce(3, System.currentTimeMillis() + c);
System.out.println("等待任务执行===========");
//消费端从redis中消费任务
//taskConsumer.consumer();
}
}
package io.renren.common.wheeltimer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Component
public class TaskConsumer implements CommandLineRunner {
@Autowired
RedisTemplate redisTemplate;
@Override
public void run(String... args) throws Exception {
Runnable runnable = ()->{
while (true) {
Set<String> taskIdSet = redisTemplate.opsForZSet().rangeByScore("delay_queue", 0, System.currentTimeMillis(), 0, 1);
if(taskIdSet == null || taskIdSet.isEmpty()){
//System.out.println("没有任务");
}else {
taskIdSet.forEach(id -> {
long result = redisTemplate.opsForZSet().remove("delay_queue",id);
if (result == 1L) {
System.out.println("从延时队列中获取到任务,taskId:" + id + " , 当前时间:" + LocalDateTime.now());
}
});
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
package io.renren.common.wheeltimer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class TaskProducer {
@Autowired
RedisTemplate redisTemplate;
public void produce(Integer taskId, long exeTime) {
System.out.println("加入任务, taskId: " + taskId + ", exeTime: " + exeTime + ", 当前时间:" + LocalDateTime.now());
redisTemplate.opsForZSet().add("delay_queue",String.valueOf(taskId),exeTime);
}
}