⾃JDK 1.5 开始,JDK提供了 ScheduledThreadPoolExecutor 类⽤于计划任务(⼜称 定时任务),这个类有两个⽤途: 在给定的延迟之后运⾏任务 周期性重复执⾏任务 在这之前,是使⽤ Timer 类来完成定时任务的,但是 Timer 有缺陷: Timer是单线程模式; 如果在执⾏任务期间某个TimerTask耗时较久,那么就会影响其它任务的调 度;Timer的任务调度是基于绝对时间的,对系统时间敏感; Timer不会捕获执⾏TimerTask时所抛出的异常,由于Timer是单线程,所以⼀ 旦出现异常,则线程就会终⽌,其他任务也得不到执⾏。 所以JDK 1.5之后,⼤家就摒弃 Timer ,使⽤ ScheduledThreadPoolExecutor 吧。
1.建立延迟队列线程池,一个核心线程就可以完成任务
//配置延时任务的线程池
private final ScheduledExecutorService executorService=new ScheduledThreadPoolExecutor(1,Executors.defaultThreadFactory());
2.使用.scheduleWithFixedDelay方法,20.秒后执行,每5秒从微信查询支付状态
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
Order redisOrder = (Order) redisTemplate.boundHashOps("Order").get(order.getId());
if (redisOrder != null) {
Result result = weixinPayFegin.queryStatus(redisOrder.getId());
Map<String, String> backMap = (Map<String, String>) result.getData();
if (backMap.get("trade_state") != null && backMap.get("trade_state").equals("SUCCESS")) {
//这是支付成功
updateStatus(backMap.get("out_trade_no"), backMap.get("pay_time"), backMap.get("transaction_id"));
} else if (backMap.get("trade_state") != null && (backMap.get("trade_state").equals("NOTPAY") || backMap.get("trade_state").equals("USERPAYING"))) {
//这是没有支付
redisTemplate.boundHashOps("Order").put(redisOrder.getId(), redisOrder);
} else {
delete(redisOrder.getId());
}
}
}
//20秒之后每5秒检查下回调信息
},10000 * 2, 5000,TimeUnit.SECONDS);
下面我说一下怎么使用redis延时队列
延迟队列可以通过 zset 来实现,因为 zset 中有一个 score,我们可以把时间作为 score,将 value 存到 redis 中,然后通过轮询的方式,去不断的读取消息出来。如果消息是一个字符串,直接发送即可,如果是一个对象,则需要对 对象进行序列化,使用 JSON 来实现序列化和反序列化。
1 封装消息
public class Message {
private String id;
private Object data;
@Override
public String toString() {
return "Message{" +
"id=" + id +
", data=" + data +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
2 封装一个消息队列:
public class DelayMsgQueue {
private Jedis jedis;
private String queue;
public DelayMsgQueue(Jedis jedis, String queue) {
this.jedis = jedis;
this.queue = queue;
}
/**
* 消息入队
* @param data 要发送的消息
*/
public void queue(Object data) {
//构造一个 Message
Message msg=new Message();
msg.setId(UUID.randomUUID().toString());
msg.setData(data);
//序列化
try {
String s = new ObjectMapper().writeValueAsString(msg);
System.out.println("msg push"+new Date());
jedis.zadd(queue,System.currentTimeMillis()+5000,s);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
/**
* 消息消费
*/
public void loop() {
while(!Thread.interrupted()){
//读取 score 在 0 到当前时间戳之间的消息
Set<String> zrange = jedis.zrangeByScore(queue, 0, System.currentTimeMillis(), 0, 1);
while(zrange.isEmpty()){
//如果消息是空的,则休息 500 毫秒然后继续
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
//如果读取到了消息,则直接读取消息出来
String next = zrange.iterator().next();
//Redis Zrem 命令用于移除有序集中的一个或多个成员,不存在的成员将被忽略。
//ZREM的返回值进行判断,只有大于0的时候,才消费数据,避免并发多消费
if(jedis.zrem(queue,next)>0){
//抢到了处理业务
try {
Message msg = new ObjectMapper().readValue(next, Message.class);
System.out.println("receive msg:" + msg);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
}
}
3测试
Jedis jedis = new Jedis("××××8×××");
//构造一个消息队列
DelayMsgQueue queue = new DelayMsgQueue(jedis,"delay-queue");
//构造消息生产者
Thread producer = new Thread(){
@Override
public void run() {
for (int i = 0; i < 5; i++) {
queue.queue("www.changgou.com>>>>" + i);
}
}
};
//构造一个消息消费者
Thread consumer = new Thread(){
@Override
public void run() {
queue.loop();
}
};
//启动
producer.start();
consumer.start();
//休息 7 秒后,停止程序
try {
Thread.sleep(7000);
consumer.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}