一次搞懂延遲任務及十種延遲任務的實現(xiàn)方式-轉(zhuǎn)載

本文是向大家介紹延遲任務的使用場景挽霉,以及延遲任務的十種實現(xiàn)方式移盆,給大家?guī)碛嘘P(guān)延遲任務的不同思路和對實現(xiàn)方式的利弊參考。



本文的主要內(nèi)容如下圖所示:

什么是延遲任務轨域?

顧明思議袱耽,我們把需要延遲執(zhí)行的任務叫做延遲任務

延遲任務的使用場景有以下這些:

紅包 24 小時未被查收干发,需要延遲執(zhí)退還業(yè)務朱巨;

每個月賬單日,需要給用戶發(fā)送當月的對賬單枉长;

訂單下單之后 30 分鐘后冀续,用戶如果沒有付錢,系統(tǒng)需要自動取消訂單必峰。

等事件都需要使用延遲任務洪唐。

延遲任務實現(xiàn)思路分析

延遲任務實現(xiàn)的關(guān)鍵是在某個時間節(jié)點執(zhí)行某個任務『鹨希基于這個信息我們可以想到實現(xiàn)延遲任務的手段有以下兩個:

自己手寫一個“死循環(huán)”一直判斷當前時間節(jié)點有沒有要執(zhí)行的任務凭需;

借助 JDK 或者第三方提供的工具類來實現(xiàn)延遲任務。

而通過 JDK 實現(xiàn)延遲任務我們能想到的關(guān)鍵詞是:DelayQueue肝匆、ScheduledExecutorService粒蜈,而第三方提供的延遲任務執(zhí)行方法就有很多了,例如:Redis旗国、Netty枯怖、MQ 等手段。

延遲任務實現(xiàn)

下面我們將結(jié)合代碼來講解每種延遲任務的具體實現(xiàn)粗仓。

1. 無限循環(huán)實現(xiàn)延遲任務

此方式我們需要開啟一個無限循環(huán)一直掃描任務,然后使用一個 Map 集合用來存儲任務和延遲執(zhí)行的時間设捐,實現(xiàn)代碼如下:

import java.time.Instant;

import java.time.LocalDateTime;

import java.util.HashMap;

import java.util.Iterator;

import java.util.Map;

/**

* 延遲任務執(zhí)行方法匯總

*/

public class DelayTaskExample {

? ? // 存放定時任務

? ? private static Map<String, Long> _TaskMap = new HashMap<>();

? ? public static void main(String[] args) {

? ? ? ? System.out.println("程序啟動時間:" + LocalDateTime.now());

? ? ? ? // 添加定時任務

? ? ? ? _TaskMap.put("task-1", Instant.now().plusSeconds(3).toEpochMilli()); // 延遲 3s

? ? ? ? // 調(diào)用無限循環(huán)實現(xiàn)延遲任務

? ? ? ? loopTask();

? ? }

? ? /**

? ? * 無限循環(huán)實現(xiàn)延遲任務

? ? */

? ? public static void loopTask() {

? ? ? ? Long itemLong = 0L;

? ? ? ? while (true) {

? ? ? ? ? ? Iterator it = _TaskMap.entrySet().iterator();

? ? ? ? ? ? while (it.hasNext()) {

? ? ? ? ? ? ? ? Map.Entry entry = (Map.Entry) it.next();

? ? ? ? ? ? ? ? itemLong = (Long) entry.getValue();

? ? ? ? ? ? ? ? // 有任務需要執(zhí)行

? ? ? ? ? ? ? ? if (Instant.now().toEpochMilli() >= itemLong) {

? ? ? ? ? ? ? ? ? ? // 延遲任務借浊,業(yè)務邏輯執(zhí)行

? ? ? ? ? ? ? ? ? ? System.out.println("執(zhí)行任務:" + entry.getKey() +

? ? ? ? ? ? ? ? ? ? ? ? ? ? " ,執(zhí)行時間:" + LocalDateTime.now());

? ? ? ? ? ? ? ? ? ? // 刪除任務

? ? ? ? ? ? ? ? ? ? _TaskMap.remove(entry.getKey());

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

以上程序執(zhí)行的結(jié)果為:

程序啟動時間:2020-04-12T18:51:28.188

執(zhí)行任務:task-1 萝招,執(zhí)行時間:2020-04-12T18:51:31.189

可以看出任務延遲了 3s 鐘執(zhí)行了蚂斤,符合我們的預期。

2. Java API 實現(xiàn)延遲任務

Java API 提供了兩種實現(xiàn)延遲任務的方法:DelayQueue 和 ScheduledExecutorService槐沼。

① ScheduledExecutorService 實現(xiàn)延遲任務

我們可以使用 ScheduledExecutorService 來以固定的頻率一直執(zhí)行任務曙蒸,實現(xiàn)代碼如下:

public class DelayTaskExample {

? ? public static void main(String[] args) {

? ? ? ? System.out.println("程序啟動時間:" + LocalDateTime.now());

? ? ? ? scheduledExecutorServiceTask();

? ? }

? ? /**

? ? * ScheduledExecutorService 實現(xiàn)固定頻率一直循環(huán)執(zhí)行任務

? ? */

? ? public static void scheduledExecutorServiceTask() {

? ? ? ? ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

? ? ? ? executor.scheduleWithFixedDelay(

? ? ? ? ? ? ? ? new Runnable() {

? ? ? ? ? ? ? ? ? ? @Override

? ? ? ? ? ? ? ? ? ? public void run() {

? ? ? ? ? ? ? ? ? ? ? ? // 執(zhí)行任務的業(yè)務代碼

? ? ? ? ? ? ? ? ? ? ? ? System.out.println("執(zhí)行任務" +

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? " ,執(zhí)行時間:" + LocalDateTime.now());

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? },

? ? ? ? ? ? ? ? 2, // 初次執(zhí)行間隔

? ? ? ? ? ? ? ? 2, // 2s 執(zhí)行一次

? ? ? ? ? ? ? ? TimeUnit.SECONDS);

? ? }

}

以上程序執(zhí)行的結(jié)果為:

程序啟動時間:2020-04-12T21:28:10.416

執(zhí)行任務 岗钩,執(zhí)行時間:2020-04-12T21:28:12.421

執(zhí)行任務 纽窟,執(zhí)行時間:2020-04-12T21:28:14.422

......

可以看出使用 ScheduledExecutorService#scheduleWithFixedDelay(...) 方法之后,會以某個頻率一直循環(huán)執(zhí)行延遲任務兼吓。

② DelayQueue 實現(xiàn)延遲任務

DelayQueue 是一個支持延時獲取元素的無界阻塞隊列臂港,隊列中的元素必須實現(xiàn) Delayed 接口,并重寫 getDelay(TimeUnit) 和 compareTo(Delayed) 方法,DelayQueue 實現(xiàn)延遲隊列的完整代碼如下:

public class DelayTest {

? ? public static void main(String[] args) throws InterruptedException {

? ? ? ? DelayQueue delayQueue = new DelayQueue();

? ? ? ? // 添加延遲任務

? ? ? ? delayQueue.put(new DelayElement(1000));

? ? ? ? delayQueue.put(new DelayElement(3000));

? ? ? ? delayQueue.put(new DelayElement(5000));

? ? ? ? System.out.println("開始時間:" +? DateFormat.getDateTimeInstance().format(new Date()));

? ? ? ? while (!delayQueue.isEmpty()){

? ? ? ? ? ? // 執(zhí)行延遲任務

? ? ? ? ? ? System.out.println(delayQueue.take());

? ? ? ? }

? ? ? ? System.out.println("結(jié)束時間:" +? DateFormat.getDateTimeInstance().format(new Date()));

? ? }

? ? static class DelayElement implements Delayed {

? ? ? ? // 延遲截止時間(單面:毫秒)

? ? ? ? long delayTime = System.currentTimeMillis();

? ? ? ? public DelayElement(long delayTime) {

? ? ? ? ? ? this.delayTime = (this.delayTime + delayTime);

? ? ? ? }

? ? ? ? @Override

? ? ? ? // 獲取剩余時間

? ? ? ? public long getDelay(TimeUnit unit) {

? ? ? ? ? ? return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);

? ? ? ? }

? ? ? ? @Override

? ? ? ? // 隊列里元素的排序依據(jù)

? ? ? ? public int compareTo(Delayed o) {

? ? ? ? ? ? if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {

? ? ? ? ? ? ? ? return 1;

? ? ? ? ? ? } else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {

? ? ? ? ? ? ? ? return -1;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? return 0;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? @Override

? ? ? ? public String toString() {

? ? ? ? ? ? return DateFormat.getDateTimeInstance().format(new Date(delayTime));

? ? ? ? }

? ? }

}

以上程序執(zhí)行的結(jié)果為:

開始時間:2020-4-12 20:40:38

2020-4-12 20:40:39

2020-4-12 20:40:41

2020-4-12 20:40:43

結(jié)束時間:2020-4-12 20:40:43

3. Redis 實現(xiàn)延遲任務

使用 Redis 實現(xiàn)延遲任務的方法大體可分為兩類:通過 zset 數(shù)據(jù)判斷的方式审孽,和通過鍵空間通知的方式县袱。

① 通過數(shù)據(jù)判斷的方式

我們借助 zset 數(shù)據(jù)類型,把延遲任務存儲在此數(shù)據(jù)集合中佑力,然后在開啟一個無線循環(huán)查詢當前時間的所有任務進行消費式散,實現(xiàn)代碼如下(需要借助 Jedis 框架):

import redis.clients.jedis.Jedis;

import utils.JedisUtils;

import java.time.Instant;

import java.util.Set;

public class DelayQueueExample {

? ? // zset key

? ? private static final String _KEY = "myDelayQueue";


? ? public static void main(String[] args) throws InterruptedException {

? ? ? ? Jedis jedis = JedisUtils.getJedis();

? ? ? ? // 延遲 30s 執(zhí)行(30s 后的時間)

? ? ? ? long delayTime = Instant.now().plusSeconds(30).getEpochSecond();

? ? ? ? jedis.zadd(_KEY, delayTime, "order_1");

? ? ? ? // 繼續(xù)添加測試數(shù)據(jù)

? ? ? ? jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");

? ? ? ? jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");

? ? ? ? jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");

? ? ? ? jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");

? ? ? ? // 開啟延遲隊列

? ? ? ? doDelayQueue(jedis);

? ? }

? ? /**

? ? * 延遲隊列消費

? ? * @param jedis Redis 客戶端

? ? */

? ? public static void doDelayQueue(Jedis jedis) throws InterruptedException {

? ? ? ? while (true) {

? ? ? ? ? ? // 當前時間

? ? ? ? ? ? Instant nowInstant = Instant.now();

? ? ? ? ? ? long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒時間

? ? ? ? ? ? long nowSecond = nowInstant.getEpochSecond();

? ? ? ? ? ? // 查詢當前時間的所有任務

? ? ? ? ? ? Set<String> data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);

? ? ? ? ? ? for (String item : data) {

? ? ? ? ? ? ? ? // 消費任務

? ? ? ? ? ? ? ? System.out.println("消費:" + item);

? ? ? ? ? ? }

? ? ? ? ? ? // 刪除已經(jīng)執(zhí)行的任務

? ? ? ? ? ? jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);

? ? ? ? ? ? Thread.sleep(1000); // 每秒輪詢一次

? ? ? ? }

? ? }

}

② 通過鍵空間通知

默認情況下 Redis 服務器端是不開啟鍵空間通知的,需要我們通過 config set notify-keyspace-events Ex 的命令手動開啟打颤,開啟鍵空間通知后暴拄,我們就可以拿到每個鍵值過期的事件,我們利用這個機制實現(xiàn)了給每個人開啟一個定時任務的功能瘸洛,實現(xiàn)代碼如下:

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPubSub;

import utils.JedisUtils;

public class TaskExample {

? ? public static final String _TOPIC = "__keyevent@0__:expired"; // 訂閱頻道名稱

? ? public static void main(String[] args) {

? ? ? ? Jedis jedis = JedisUtils.getJedis();

? ? ? ? // 執(zhí)行定時任務

? ? ? ? doTask(jedis);

? ? }

? ? /**

? ? * 訂閱過期消息揍移,執(zhí)行定時任務

? ? * @param jedis Redis 客戶端

? ? */

? ? public static void doTask(Jedis jedis) {

? ? ? ? // 訂閱過期消息

? ? ? ? jedis.psubscribe(new JedisPubSub() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onPMessage(String pattern, String channel, String message) {

? ? ? ? ? ? ? ? // 接收到消息,執(zhí)行定時任務

? ? ? ? ? ? ? ? System.out.println("收到消息:" + message);

? ? ? ? ? ? }

? ? ? ? }, _TOPIC);

? ? }

}

4. Netty 實現(xiàn)延遲任務

Netty 是由 JBOSS 提供的一個 Java 開源框架反肋,它是一個基于 NIO 的客戶那伐、服務器端的編程框架,使用 Netty 可以確保你快速和簡單的開發(fā)出一個網(wǎng)絡應用石蔗,例如實現(xiàn)了某種協(xié)議的客戶罕邀、服務端應用。Netty 相當于簡化和流線化了網(wǎng)絡應用的編程開發(fā)過程养距,例如:基于 TCP 和 UDP 的 socket 服務開發(fā)诉探。

可以使用 Netty 提供的工具類 HashedWheelTimer 來實現(xiàn)延遲任務,實現(xiàn)代碼如下棍厌。

首先在項目中添加 Netty 引用肾胯,配置如下:

<!-- https://mvnrepository.com/artifact/io.netty/netty-common -->

<dependency>

? ? <groupId>io.netty</groupId>

? ? <artifactId>netty-common</artifactId>

? ? <version>4.1.48.Final</version>

</dependency>

Netty 實現(xiàn)的完整代碼如下:

public class DelayTaskExample {

? ? public static void main(String[] args) {

? ? ? ? System.out.println("程序啟動時間:" + LocalDateTime.now());

? ? ? ? NettyTask();

? ? }

? ? /**

? ? * 基于 Netty 的延遲任務

? ? */

? ? private static void NettyTask() {

? ? ? ? // 創(chuàng)建延遲任務實例

? ? ? ? HashedWheelTimer timer = new HashedWheelTimer(3, // 時間間隔

? ? ? ? ? ? ? ? TimeUnit.SECONDS,

? ? ? ? ? ? ? ? 100); // 時間輪中的槽數(shù)

? ? ? ? // 創(chuàng)建一個任務

? ? ? ? TimerTask task = new TimerTask() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void run(Timeout timeout) throws Exception {

? ? ? ? ? ? ? ? System.out.println("執(zhí)行任務" +

? ? ? ? ? ? ? ? ? ? ? ? " ,執(zhí)行時間:" + LocalDateTime.now());

? ? ? ? ? ? }

? ? ? ? };

? ? ? ? // 將任務添加到延遲隊列中

? ? ? ? timer.newTimeout(task, 0, TimeUnit.SECONDS);

? ? }

}

以上程序執(zhí)行的結(jié)果為:

程序啟動時間:2020-04-13T10:16:23.033

執(zhí)行任務 耘纱,執(zhí)行時間:2020-04-13T10:16:26.118

HashedWheelTimer 是使用定時輪實現(xiàn)的敬肚,定時輪其實就是一種環(huán)型的數(shù)據(jù)結(jié)構(gòu),可以把它想象成一個時鐘束析,分成了許多格子艳馒,每個格子代表一定的時間,在這個格子上用一個鏈表來保存要執(zhí)行的超時任務员寇,同時有一個指針一格一格的走弄慰,走到那個格子時就執(zhí)行格子對應的延遲任務,如下圖所示:

(圖片來源于網(wǎng)絡)

以上的圖片可以理解為蝶锋,時間輪大小為 8陆爽,某個時間轉(zhuǎn)一格(例如 1s),每格指向一個鏈表扳缕,保存著待執(zhí)行的任務墓陈。

5. MQ 實現(xiàn)延遲任務

如果專門開啟一個 MQ 中間件來執(zhí)行延遲任務恶守,就有點殺雞用宰牛刀般的奢侈了,不過已經(jīng)有了 MQ 環(huán)境的話贡必,用它來實現(xiàn)延遲任務的話兔港,還是可取的。

幾乎所有的 MQ 中間件都可以實現(xiàn)延遲任務仔拟,在這里更準確的叫法應該叫延隊列衫樊。本文就使用 RabbitMQ 為例,來看它是如何實現(xiàn)延遲任務的利花。

RabbitMQ 實現(xiàn)延遲隊列的方式有兩種:

通過消息過期后進入死信交換器科侈,再由交換器轉(zhuǎn)發(fā)到延遲消費隊列,實現(xiàn)延遲功能炒事;

使用 rabbitmq-delayed-message-exchange 插件實現(xiàn)延遲功能臀栈。

注意: 延遲插件 rabbitmq-delayed-message-exchange 是在 RabbitMQ 3.5.7 及以上的版本才支持的,依賴 Erlang/OPT 18.0 及以上運行環(huán)境挠乳。

由于使用死信交換器比較麻煩权薯,所以推薦使用第二種實現(xiàn)方式 rabbitmq-delayed-message-exchange 插件的方式實現(xiàn)延遲隊列的功能。

首先睡扬,我們需要下載并安裝 rabbitmq-delayed-message-exchange 插件盟蚣,下載地址:http://www.rabbitmq.com/community-plugins.html

選擇相應的對應的版本進行下載,然后拷貝到 RabbitMQ 服務器目錄卖怜,使用命令 rabbitmq-plugins enable rabbitmq_delayed_message_exchange 開啟插件屎开,在使用命令 rabbitmq-plugins list 查詢安裝的所有插件,安裝成功如下圖所示:

最后重啟 RabbitMQ 服務马靠,使插件生效奄抽。

首先,我們先要配置消息隊列甩鳄,實現(xiàn)代碼如下:

import com.example.rabbitmq.mq.DirectConfig;

import org.springframework.amqp.core.*;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

import java.util.Map;

@Configuration

public class DelayedConfig {

? ? final static String QUEUE_NAME = "delayed.goods.order";

? ? final static String EXCHANGE_NAME = "delayedec";

? ? @Bean

? ? public Queue queue() {

? ? ? ? return new Queue(DelayedConfig.QUEUE_NAME);

? ? }

? ? // 配置默認的交換機

? ? @Bean

? ? CustomExchange customExchange() {

? ? ? ? Map<String, Object> args = new HashMap<>();

? ? ? ? args.put("x-delayed-type", "direct");

? ? ? ? //參數(shù)二為類型:必須是x-delayed-message

? ? ? ? return new CustomExchange(DelayedConfig.EXCHANGE_NAME, "x-delayed-message", true, false, args);

? ? }

? ? // 綁定隊列到交換器

? ? @Bean

? ? Binding binding(Queue queue, CustomExchange exchange) {

? ? ? ? return BindingBuilder.bind(queue).to(exchange).with(DelayedConfig.QUEUE_NAME).noargs();

? ? }

}

然后添加增加消息的代碼逞度,具體實現(xiàn)如下:

import org.springframework.amqp.AmqpException;

import org.springframework.amqp.core.AmqpTemplate;

import org.springframework.amqp.core.Message;

import org.springframework.amqp.core.MessagePostProcessor;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;

import java.util.Date;

@Component

public class DelayedSender {

? ? @Autowired

? ? private AmqpTemplate rabbitTemplate;

? ? public void send(String msg) {

? ? ? ? SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

? ? ? ? System.out.println("發(fā)送時間:" + sf.format(new Date()));

? ? ? ? rabbitTemplate.convertAndSend(DelayedConfig.EXCHANGE_NAME, DelayedConfig.QUEUE_NAME, msg, new MessagePostProcessor() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public Message postProcessMessage(Message message) throws AmqpException {

? ? ? ? ? ? ? ? message.getMessageProperties().setHeader("x-delay", 3000);

? ? ? ? ? ? ? ? return message;

? ? ? ? ? ? }

? ? ? ? });

? ? }

}

再添加消費消息的代碼:

import org.springframework.amqp.rabbit.annotation.RabbitHandler;

import org.springframework.amqp.rabbit.annotation.RabbitListener;

import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;

import java.util.Date;

@Component

@RabbitListener(queues = "delayed.goods.order")

public class DelayedReceiver {

? ? @RabbitHandler

? ? public void process(String msg) {

? ? ? ? SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

? ? ? ? System.out.println("接收時間:" + sdf.format(new Date()));

? ? ? ? System.out.println("消息內(nèi)容:" + msg);

? ? }

}

最后,我們使用代碼測試一下:

import com.example.rabbitmq.RabbitmqApplication;

import com.example.rabbitmq.mq.delayed.DelayedSender;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

import java.text.SimpleDateFormat;

import java.util.Date;

@RunWith(SpringRunner.class)

@SpringBootTest

public class DelayedTest {

? ? @Autowired

? ? private DelayedSender sender;

? ? @Test

? ? public void Test() throws InterruptedException {

? ? ? ? SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");

? ? ? ? sender.send("Hi Admin.");

? ? ? ? Thread.sleep(5 * 1000); //等待接收程序執(zhí)行之后娩贷,再退出測試

? ? }

}

以上程序的執(zhí)行結(jié)果如下:

發(fā)送時間:2020-04-13 20:47:51

接收時間:2020-04-13 20:47:54

消息內(nèi)容:Hi Admin.

從結(jié)果可以看出第晰,以上程序執(zhí)行符合延遲任務的實現(xiàn)預期锁孟。

6. 使用 Spring 定時任務

如果你使用的是 Spring 或 SpringBoot 的項目的話彬祖,可以使用借助 Scheduled 來實現(xiàn),本文將使用 SpringBoot 項目來演示 Scheduled 的實現(xiàn)品抽,實現(xiàn)我們需要聲明開啟 Scheduled储笑,實現(xiàn)代碼如下:

@SpringBootApplication

@EnableScheduling

public class Application {

? ? public static void main(String[] args) {

? ? ? ? SpringApplication.run(Application.class, args);

? ? }

}

然后添加延遲任務,實現(xiàn)代碼如下:

@Component

public class ScheduleJobs {

? ? @Scheduled(fixedDelay = 2 * 1000)

? ? public void fixedDelayJob() throws InterruptedException {

? ? ? ? System.out.println("任務執(zhí)行圆恤,時間:" + LocalDateTime.now());

? ? }

}

此時當我們啟動項目之后就可以看到任務以延遲了 2s 的形式一直循環(huán)執(zhí)行突倍,結(jié)果如下:

任務執(zhí)行,時間:2020-04-13T14:07:53.349

任務執(zhí)行,時間:2020-04-13T14:07:55.350

任務執(zhí)行羽历,時間:2020-04-13T14:07:57.351

...

我們也可以使用 Corn 表達式來定義任務執(zhí)行的頻率焊虏,例如使用 @Scheduled(cron = "0/4 * * * * ?") 。

7. Quartz 實現(xiàn)延遲任務

Quartz 是一款功能強大的任務調(diào)度器秕磷,可以實現(xiàn)較為復雜的調(diào)度功能诵闭,它還支持分布式的任務調(diào)度。

我們使用 Quartz 來實現(xiàn)一個延遲任務澎嚣,首先定義一個執(zhí)行任務代碼如下:

import org.quartz.JobExecutionContext;

import org.quartz.JobExecutionException;

import org.springframework.scheduling.quartz.QuartzJobBean;

import java.time.LocalDateTime;

public class SampleJob extends QuartzJobBean {

? ? @Override

? ? protected void executeInternal(JobExecutionContext jobExecutionContext)

? ? ? ? ? ? throws JobExecutionException {

? ? ? ? System.out.println("任務執(zhí)行疏尿,時間:" + LocalDateTime.now());

? ? }

}

在定義一個 JobDetail 和 Trigger 實現(xiàn)代碼如下:

import org.quartz.*;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class SampleScheduler {

? ? @Bean

? ? public JobDetail sampleJobDetail() {

? ? ? ? return JobBuilder.newJob(SampleJob.class).withIdentity("sampleJob")

? ? ? ? ? ? ? ? .storeDurably().build();

? ? }

? ? @Bean

? ? public Trigger sampleJobTrigger() {

? ? ? ? // 3s 后執(zhí)行

? ? ? ? SimpleScheduleBuilder scheduleBuilder =

? ? ? ? ? ? ? ? SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).withRepeatCount(1);

? ? ? ? return TriggerBuilder.newTrigger().forJob(sampleJobDetail()).withIdentity("sampleTrigger")

? ? ? ? ? ? ? ? .withSchedule(scheduleBuilder).build();

? ? }

}

最后在 SpringBoot 項目啟動之后開啟延遲任務,實現(xiàn)代碼如下:

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.CommandLineRunner;

import org.springframework.scheduling.quartz.SchedulerFactoryBean;

/**

* SpringBoot 項目啟動后執(zhí)行

*/

public class MyStartupRunner implements CommandLineRunner {

? ? @Autowired

? ? private SchedulerFactoryBean schedulerFactoryBean;

? ? @Autowired

? ? private SampleScheduler sampleScheduler;

? ? @Override

? ? public void run(String... args) throws Exception {

? ? ? ? // 啟動定時任務

? ? ? ? schedulerFactoryBean.getScheduler().scheduleJob(

? ? ? ? ? ? ? ? sampleScheduler.sampleJobTrigger());

? ? }

}

以上程序的執(zhí)行結(jié)果如下:

2020-04-13 19:02:12.331? INFO 17768 --- [? restartedMain] com.example.demo.DemoApplication? ? ? ? : Started DemoApplication in 1.815 seconds (JVM running for 3.088)

任務執(zhí)行易桃,時間:2020-04-13T19:02:15.019

從結(jié)果可以看出在項目啟動 3s 之后執(zhí)行了延遲任務褥琐。


原文鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市晤郑,隨后出現(xiàn)的幾起案子敌呈,更是在濱河造成了極大的恐慌,老刑警劉巖贩汉,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驱富,死亡現(xiàn)場離奇詭異,居然都是意外死亡匹舞,警方通過查閱死者的電腦和手機褐鸥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赐稽,“玉大人叫榕,你說我怎么就攤上這事℃⒍妫” “怎么了晰绎?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長括丁。 經(jīng)常有香客問我荞下,道長,這世上最難降的妖魔是什么史飞? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任尖昏,我火速辦了婚禮,結(jié)果婚禮上构资,老公的妹妹穿的比我還像新娘抽诉。我一直安慰自己,他們只是感情好吐绵,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布迹淌。 她就那樣靜靜地躺著河绽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪唉窃。 梳的紋絲不亂的頭發(fā)上耙饰,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音纹份,去河邊找鬼榔幸。 笑死,一個胖子當著我的面吹牛矮嫉,可吹牛的內(nèi)容都是我干的削咆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼蠢笋,長吁一口氣:“原來是場噩夢啊……” “哼拨齐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起昨寞,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瞻惋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后援岩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歼狼,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年享怀,在試婚紗的時候發(fā)現(xiàn)自己被綠了羽峰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡添瓷,死狀恐怖梅屉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鳞贷,我是刑警寧澤坯汤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站搀愧,受9級特大地震影響惰聂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咱筛,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一搓幌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧眷蚓,春花似錦鼻种、人聲如沸反番。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至篙贸,卻和暖如春投队,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爵川。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工敷鸦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寝贡。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓扒披,卻偏偏與公主長得像,于是被迫代替她去往敵國和親圃泡。 傳聞我的和親對象是個殘疾皇子碟案,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容