业务场景:
1、生成订单 30 分钟未支付,则自动取消(延时任务)
2、生成订单 60 秒后,给用户发短信(延时任务)
…
1
| 24-08-18 初始记录(从原先的笔记进行搬运)
|
定时任务与延时任务的区别
解决思路
数据库定时轮询(定时任务)
通常在小型项目中使用,即通过一个线程定时的去扫描数据库,通过订单时间来判断是否有超时的订单,然后进行 update 或 delete 等操作。
实现
通过 springBoot 中的@Schedule 进行实现
步骤
-
在启动类上添加注解@EnableScheduling
1 2 3 4 5 6 7
| @SpringBootApplication @EnableScheduling public class ScheduleAppcation { public static void main(String[] args) { SpringApplication.run(ScheduleAppcation.class, args); } }
|
-
在目标类上添加注解
1 2 3 4 5
| @Scheduled(fixedDelay = 10 * 1000) public void testJob01() { ... }
|
1 2 3 4 5
| @Scheduled(cron = "0 0 6 * * ?") public void testJob01() { ... }
|
优缺点
JDK 的延迟队列
该方案是利用 JDK 自带的 DelayQueue 来实现,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中获取元素,放入 DelayQueue 中的对象,是必须实现 Delayed 接口的。
DelayQueue 属于排序队列,它的特殊之处在于队列的元素必须实现 Delayed 接口,该接口需要实现 compareTo 和 getDelay 方法
getDelay 方法:获取元素在队列中的剩余时间,只有当剩余时间为 0 时元素才可以出队列。
compareTo 方法:用于排序,确定元素出队列的顺序。
实现
利用 JDK 自带的 DelayQueue 来实现
步骤
-
在测试包 jdk 下创建延迟任务元素对象 DelayedTask,实现 compareTo 和 getDelay 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class DelayedTask implements Delayed{ private int executeTime = 0; public DelayedTask(int delay){ Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.SECOND,delay); this.executeTime = (int)(calendar.getTimeInMillis() /1000 ); }
@Override public long getDelay(TimeUnit unit) { Calendar calendar = Calendar.getInstance(); return executeTime - (calendar.getTimeInMillis()/1000); }
@Override public int compareTo(Delayed o) { long val = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS); return val == 0 ? 0 : ( val < 0 ? -1: 1 ); } }
|
-
在 main 方法中创建 DelayQueue 并向延迟队列中添加三个延迟任务
-
循环的从延迟队列中拉取任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public static void main(String[] args) { DelayQueue<DelayedTask> queue = new DelayQueue<DelayedTask>(); queue.add(new DelayedTask(5)); queue.add(new DelayedTask(10)); queue.add(new DelayedTask(15));
System.out.println(System.currentTimeMillis()/1000+" start consume "); while(queue.size() != 0){ DelayedTask delayedTask = queue.poll(); if(delayedTask !=null ){ System.out.println(System.currentTimeMillis()/1000+" cosume task"); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
|
优缺点
Redis 缓存
实现
利用 Redis 中 Key 的过期时间
步骤
-
给 Redis 中 Key 设置过期时间
-
监听 Redis 中 Key 过期事件
-
获取过期 Key 对应的值进行消费
- 过期 Key 拿不到值
- 可以把信息存储到 Key 上(监听事件可以获取到即将过期的 key,可以将文章 id 存储到 redis 中)
- 存储一份不过期的对应 Key,在 Key 过期时获取这个不过期 Key 取值再删除
优缺点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package com.heima.common.redislock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import java.util.UUID; import java.util.concurrent.TimeUnit; public class RedisLockImpl implements RedisLock { @Autowired private StringRedisTemplate stringRedisTemplate; private ThreadLocal<String> threadLocal = new ThreadLocal<String>(); private ThreadLocal<Integer> threadLocalInteger = new ThreadLocal<Integer>(); @Override public boolean tryLock(String key, long timeout, TimeUnit unit) { Boolean isLocked = false; if (threadLocal.get() == null) { String uuid = UUID.randomUUID().toString(); threadLocal.set(uuid); isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit); } else { isLocked = true; } if (isLocked) { Integer count = threadLocalInteger.get() == null ? 0 : threadLocalInteger.get(); threadLocalInteger.set(count++); } return isLocked; } @Override public void releaseLock(String key) { if (threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))) { Integer count = threadLocalInteger.get(); if (count == null || --count <= 0) { stringRedisTemplate.delete(key); } } } }
|
使用 MQ 实现延时任务
实现
内链:[[消息队列的选型与优缺点]]
外链:消息队列的选型与优缺点
优缺点