1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        再見 Spring Task,這個(gè)定時(shí)任務(wù)框架真香!

        共 8410字,需瀏覽 17分鐘

         ·

        2021-11-08 18:25

        最近有朋友問到定時(shí)任務(wù)相關(guān)的問題。

        于是,我簡單寫了一篇文章總結(jié)一下定時(shí)任務(wù)的一些概念以及一些常見的定時(shí)任務(wù)技術(shù)選型。希望能對(duì)小伙伴們有幫助!

        為什么需要定時(shí)任務(wù)?

        我們來看一下幾個(gè)非常常見的業(yè)務(wù)場景:

        1. 某系統(tǒng)凌晨要進(jìn)行數(shù)據(jù)備份。
        2. 某電商平臺(tái),用戶下單半個(gè)小時(shí)未支付的情況下需要自動(dòng)取消訂單。
        3. 某媒體聚合平臺(tái),每 10 分鐘動(dòng)態(tài)抓取某某網(wǎng)站的數(shù)據(jù)為自己所用。
        4. 某博客平臺(tái),支持定時(shí)發(fā)送文章。
        5. 某基金平臺(tái),每晚定時(shí)計(jì)算用戶當(dāng)日收益情況并推送給用戶最新的數(shù)據(jù)。
        6. ......

        這些場景往往都要求我們?cè)谀硞€(gè)特定的時(shí)間去做某個(gè)事情。

        單機(jī)定時(shí)任務(wù)技術(shù)選型

        Timer

        java.util.Timer是 JDK 1.3 開始就已經(jīng)支持的一種定時(shí)任務(wù)的實(shí)現(xiàn)方式。

        Timer 內(nèi)部使用一個(gè)叫做 TaskQueue 的類存放定時(shí)任務(wù),它是一個(gè)基于最小堆實(shí)現(xiàn)的優(yōu)先級(jí)隊(duì)列。TaskQueue 會(huì)按照任務(wù)距離下一次執(zhí)行時(shí)間的大小將任務(wù)排序,保證在堆頂?shù)娜蝿?wù)最先執(zhí)行。這樣在需要執(zhí)行任務(wù)時(shí),每次只需要取出堆頂?shù)娜蝿?wù)運(yùn)行即可!

        Timer 使用起來比較簡單,通過下面的方式我們就能創(chuàng)建一個(gè) 1s 之后執(zhí)行的定時(shí)任務(wù)。

        //?示例代碼:
        TimerTask?task?=?new?TimerTask()?{
        ????public?void?run()?{
        ????????System.out.println("當(dāng)前時(shí)間:?"?+?new?Date()?+?"n"?+
        ????????????????"線程名稱:?"?+?Thread.currentThread().getName());
        ????}
        };
        System.out.println("當(dāng)前時(shí)間:?"?+?new?Date()?+?"n"?+
        ????????"線程名稱:?"?+?Thread.currentThread().getName());
        Timer?timer?=?new?Timer("Timer");
        long?delay?=?1000L;
        timer.schedule(task,?delay);


        //輸出:
        當(dāng)前時(shí)間:?Fri?May?28?15:18:47?CST?2021n線程名稱:?main
        當(dāng)前時(shí)間:?Fri?May?28?15:18:48?CST?2021n線程名稱:?Timer

        不過其缺陷較多,比如一個(gè) Timer 一個(gè)線程,這就導(dǎo)致 Timer 的任務(wù)的執(zhí)行只能串行執(zhí)行,一個(gè)任務(wù)執(zhí)行時(shí)間過長的話會(huì)影響其他任務(wù)(性能非常差),再比如發(fā)生異常時(shí)任務(wù)直接停止(Timer 只捕獲了 InterruptedException )。

        Timer 類上的有一段注釋是這樣寫的:

        ?*?This?class?does?not?offer?real-time?guarantees:?it?schedules
        ?*?tasks?using?the?<tt>Object.wait(long)tt
        >?method.
        ?*Java?5.0?introduced?the?{@code?java.util.concurrent}?package?and
        ?*?one?of?the?concurrency?utilities?therein?is?the?{@link
        ?*?java.util.concurrent.ScheduledThreadPoolExecutor
        ?*?ScheduledThreadPoolExecutor}?which?is?a?thread?pool?for?repeatedly
        ?*?executing?tasks?at?a?given?rate?or?delay.??It?is?effectively?a?more
        ?*?versatile?replacement?for?the?{@code?Timer}/{@code?TimerTask}
        ?*?combination,?as?it?allows?multiple?service?threads,?accepts?various
        ?*?time?units,?and?doesn't?require?subclassing?{@code?TimerTask}?(just
        ?*?implement?{@code?Runnable}).??Configuring?{@code
        ?*?ScheduledThreadPoolExecutor}?with?one?thread?makes?it?equivalent?to
        ?*?{@code?Timer}.

        大概的意思就是:ScheduledThreadPoolExecutor 支持多線程執(zhí)行定時(shí)任務(wù)并且功能更強(qiáng)大,是 Timer 的替代品。

        ScheduledExecutorService

        ScheduledExecutorService 是一個(gè)接口,有多個(gè)實(shí)現(xiàn)類,比較常用的是 ScheduledThreadPoolExecutor 。

        ScheduledThreadPoolExecutor 本身就是一個(gè)線程池,支持任務(wù)并發(fā)執(zhí)行。并且,其內(nèi)部使用 DelayQueue 作為任務(wù)隊(duì)列。

        //?示例代碼:
        TimerTask?repeatedTask?=?new?TimerTask()?{
        ????@SneakyThrows
        ????public?void?run()?{
        ????????System.out.println("當(dāng)前時(shí)間:?"?+?new?Date()?+?"n"?+
        ????????????????"線程名稱:?"?+?Thread.currentThread().getName());
        ????}
        };
        System.out.println("當(dāng)前時(shí)間:?"?+?new?Date()?+?"n"?+
        ????????"線程名稱:?"?+?Thread.currentThread().getName());
        ScheduledExecutorService?executor?=?Executors.newScheduledThreadPool(3);
        long?delay??=?1000L;
        long?period?=?1000L;
        executor.scheduleAtFixedRate(repeatedTask,?delay,?period,?TimeUnit.MILLISECONDS);
        Thread.sleep(delay?+?period?*?5);
        executor.shutdown();
        //輸出:
        當(dāng)前時(shí)間:?Fri?May?28?15:40:46?CST?2021n線程名稱:?main
        當(dāng)前時(shí)間:?Fri?May?28?15:40:47?CST?2021n線程名稱:?pool-1-thread-1
        當(dāng)前時(shí)間:?Fri?May?28?15:40:48?CST?2021n線程名稱:?pool-1-thread-1
        當(dāng)前時(shí)間:?Fri?May?28?15:40:49?CST?2021n線程名稱:?pool-1-thread-2
        當(dāng)前時(shí)間:?Fri?May?28?15:40:50?CST?2021n線程名稱:?pool-1-thread-2
        當(dāng)前時(shí)間:?Fri?May?28?15:40:51?CST?2021n線程名稱:?pool-1-thread-2
        當(dāng)前時(shí)間:?Fri?May?28?15:40:52?CST?2021n線程名稱:?pool-1-thread-2

        不論是使用 Timer 還是 ScheduledExecutorService 都無法使用 Cron 表達(dá)式指定任務(wù)執(zhí)行的具體時(shí)間。

        Spring Task

        我們直接通過 Spring 提供的 @Scheduled 注解即可定義定時(shí)任務(wù),非常方便!

        /**
        ?* cron:使用Cron表達(dá)式。?每分鐘的1,2秒運(yùn)行
        ?*/

        @Scheduled(cron?=?"1-2?*?*?*?*???")
        public?void?reportCurrentTimeWithCronExpression()?{
        ??log.info("Cron?Expression:?The?time?is?now?{}",?dateFormat.format(new?Date()));
        }

        我在大學(xué)那會(huì)做的一個(gè) SSM 的企業(yè)級(jí)項(xiàng)目,就是用的 Spring Task 來做的定時(shí)任務(wù)。

        并且,Spring Task 還是支持 Cron 表達(dá)式 的。Cron 表達(dá)式主要用于定時(shí)作業(yè)(定時(shí)任務(wù))系統(tǒng)定義執(zhí)行時(shí)間或執(zhí)行頻率的表達(dá)式,非常厲害,你可以通過 Cron 表達(dá)式進(jìn)行設(shè)置定時(shí)任務(wù)每天或者每個(gè)月什么時(shí)候執(zhí)行等等操作。咱們要學(xué)習(xí)定時(shí)任務(wù)的話,Cron 表達(dá)式是一定是要重點(diǎn)關(guān)注的。推薦一個(gè)在線 Cron 表達(dá)式生成器:http://cron.qqe2.com/ 。

        但是,Spring 自帶的定時(shí)調(diào)度只支持單機(jī),并且提供的功能比較單一。

        Spring Task 底層是基于 JDK 的 ScheduledThreadPoolExecutor 線程池來實(shí)現(xiàn)的。

        優(yōu)缺點(diǎn)總結(jié):

        • 優(yōu)點(diǎn):簡單,輕量,支持 Cron 表達(dá)式
        • 缺點(diǎn) :功能單一

        時(shí)間輪

        Kafka、Dubbo、ZooKeeper、Netty 、Caffeine 、Akka 中都有對(duì)時(shí)間輪的實(shí)現(xiàn)。

        時(shí)間輪簡單來說就是一個(gè)環(huán)形的隊(duì)列(底層一般基于數(shù)組實(shí)現(xiàn)),隊(duì)列中的每一個(gè)元素(時(shí)間格)都可以存放一個(gè)定時(shí)任務(wù)列表。

        時(shí)間輪中的每個(gè)時(shí)間格代表了時(shí)間輪的基本時(shí)間跨度或者說時(shí)間精度,加入時(shí)間一秒走一個(gè)時(shí)間格的話,那么這個(gè)時(shí)間輪的最高精度就是 1 秒(也就是說 3 s 和 3.9s 會(huì)在同一個(gè)時(shí)間格中)。

        下圖是一個(gè)有 12 個(gè)時(shí)間格的時(shí)間輪,轉(zhuǎn)完一圈需要 12 s。當(dāng)我們需要新建一個(gè) 3s 后執(zhí)行的定時(shí)任務(wù),只需要將定時(shí)任務(wù)放在下標(biāo)為 3 的時(shí)間格中即可。當(dāng)我們需要新建一個(gè) 9s 后執(zhí)行的定時(shí)任務(wù),只需要將定時(shí)任務(wù)放在下標(biāo)為 9 的時(shí)間格中即可。

        那當(dāng)我們需要?jiǎng)?chuàng)建一個(gè) 13s 后執(zhí)行的定時(shí)任務(wù)怎么辦呢?這個(gè)時(shí)候可以引入一叫做 圈數(shù)/輪數(shù) 的概念,也就是說這個(gè)任務(wù)還是放在下標(biāo)為 3 的時(shí)間格中, 不過它的圈數(shù)為 2 。

        除了增加圈數(shù)這種方法之外,還有一種 多層次時(shí)間輪 (類似手表),Kafka 采用的就是這種方案。

        針對(duì)下圖的時(shí)間輪,我來舉一個(gè)例子便于大家理解。

        上圖的時(shí)間輪,第 1 層的時(shí)間精度為 1 ,第 2 層的時(shí)間精度為 20 ,第 3 層的時(shí)間精度為 400。假如我們需要添加一個(gè) 350s 后執(zhí)行的任務(wù) A 的話(當(dāng)前時(shí)間是 0s),這個(gè)任務(wù)會(huì)被放在第 2 層(因?yàn)榈诙拥臅r(shí)間跨度為 20*20=400>350)的第 350/20=17 個(gè)時(shí)間格子。

        當(dāng)?shù)谝粚愚D(zhuǎn)了 17 圈之后,時(shí)間過去了 340s ,第 2 層的指針此時(shí)來到第 17 個(gè)時(shí)間格子。此時(shí),第 2 層第 17 個(gè)格子的任務(wù)會(huì)被移動(dòng)到第 1 層。

        任務(wù) A 當(dāng)前是 10s 之后執(zhí)行,因此它會(huì)被移動(dòng)到第 1 層的第 10 個(gè)時(shí)間格子。

        這里在層與層之間的移動(dòng)也叫做時(shí)間輪的升降級(jí)。參考手表來理解就好!

        時(shí)間輪比較適合任務(wù)數(shù)量比較多的定時(shí)任務(wù)場景,它的任務(wù)寫入和執(zhí)行的時(shí)間復(fù)雜度都是 0(1)。

        分布式定時(shí)任務(wù)技術(shù)選型

        上面提到的一些定時(shí)任務(wù)的解決方案都是在單機(jī)下執(zhí)行的,適用于比較簡單的定時(shí)任務(wù)場景比如每天凌晨備份一次數(shù)據(jù)。

        如果我們需要一些高級(jí)特性比如支持任務(wù)在分布式場景下的分片和高可用的話,我們就需要用到分布式任務(wù)調(diào)度框架了。

        通常情況下,一個(gè)定時(shí)任務(wù)的執(zhí)行往往涉及到下面這些角色:

        • 任務(wù) :首先肯定是要執(zhí)行的任務(wù),這個(gè)任務(wù)就是具體的業(yè)務(wù)邏輯比如定時(shí)發(fā)送文章。
        • 調(diào)度器 :其次是調(diào)度中心,調(diào)度中心主要負(fù)責(zé)任務(wù)管理,會(huì)分配任務(wù)給執(zhí)行器。
        • 執(zhí)行器 :最后就是執(zhí)行器,執(zhí)行器接收調(diào)度器分派的任務(wù)并執(zhí)行。

        Quartz

        一個(gè)很火的開源任務(wù)調(diào)度框架,完全由Java寫成。Quartz 可以說是 Java 定時(shí)任務(wù)領(lǐng)域的老大哥或者說參考標(biāo)準(zhǔn),其他的任務(wù)調(diào)度框架基本都是基于 Quartz 開發(fā)的,比如當(dāng)當(dāng)網(wǎng)的elastic-job就是基于quartz二次開發(fā)之后的分布式調(diào)度解決方案。

        使用 Quartz 可以很方便地與 Spring 集成,并且支持動(dòng)態(tài)添加任務(wù)和集群。但是,Quartz 使用起來也比較麻煩,API 繁瑣。

        并且,Quzrtz 并沒有內(nèi)置 UI 管理控制臺(tái),不過你可以使用 quartzui 這個(gè)開源項(xiàng)目來解決這個(gè)問題。

        另外,Quartz 雖然也支持分布式任務(wù)。但是,它是在數(shù)據(jù)庫層面,通過數(shù)據(jù)庫的鎖機(jī)制做的,有非常多的弊端比如系統(tǒng)侵入性嚴(yán)重、節(jié)點(diǎn)負(fù)載不均衡。有點(diǎn)偽分布式的味道。

        優(yōu)缺點(diǎn)總結(jié):

        • 優(yōu)點(diǎn):可以與 Spring 集成,并且支持動(dòng)態(tài)添加任務(wù)和集群。
        • 缺點(diǎn) :分布式支持不友好,沒有內(nèi)置 UI 管理控制臺(tái)、使用麻煩(相比于其他同類型框架來說)

        Elastic-Job

        Elastic-Job 是當(dāng)當(dāng)網(wǎng)開源的一個(gè)基于QuartzZooKeeper的分布式調(diào)度解決方案,由兩個(gè)相互獨(dú)立的子項(xiàng)目 Elastic-Job-LiteElastic-Job-Cloud 組成,一般我們只要使用 Elastic-Job-Lite 就好。

        ElasticJob 支持任務(wù)在分布式場景下的分片和高可用、任務(wù)可視化管理等功能。

        ElasticJob-Lite 的架構(gòu)設(shè)計(jì)如下圖所示:

        從上圖可以看出,Elastic-Job 沒有調(diào)度中心這一概念,而是使用 ZooKeeper 作為注冊(cè)中心,注冊(cè)中心負(fù)責(zé)協(xié)調(diào)分配任務(wù)到不同的節(jié)點(diǎn)上。

        Elastic-Job 中的定時(shí)調(diào)度都是由執(zhí)行器自行觸發(fā),這種設(shè)計(jì)也被稱為去中心化設(shè)計(jì)(調(diào)度和處理都是執(zhí)行器單獨(dú)完成)。

        @Component
        @ElasticJobConf(name?=?"dayJob",?cron?=?"0/10?*?*?*?*??",?shardingTotalCount?=?2,
        ????????shardingItemParameters?=?"0=AAAA,1=BBBB",?description?=?"簡單任務(wù)",?failover?=?true)
        public?class?TestJob?implements?SimpleJob?{
        ????@Override
        ????public?void?execute(ShardingContext?shardingContext)?{
        ????????log.info("TestJob任務(wù)名:【{}】, 片數(shù):【{}】, param=【{}】",?shardingContext.getJobName(),?shardingContext.getShardingTotalCount(),
        ????????????????shardingContext.getShardingParameter());
        ????}
        }

        相關(guān)地址:

        • Github 地址:https://github.com/apache/shardingsphere-elasticjob。
        • 官方網(wǎng)站:https://shardingsphere.apache.org/elasticjob/index_zh.html 。

        優(yōu)缺點(diǎn)總結(jié):

        • 優(yōu)點(diǎn) :可以與 Spring 集成、支持分布式、支持集群、性能不錯(cuò)
        • 缺點(diǎn) :依賴了額外的中間件比如 Zookeeper(復(fù)雜度增加,可靠性降低、維護(hù)成本變高)

        XXL-JOB

        XXL-JOB 于 2015 年開源,是一款優(yōu)秀的輕量級(jí)分布式任務(wù)調(diào)度框架,支持任務(wù)可視化管理、彈性擴(kuò)容縮容、任務(wù)失敗重試和告警、任務(wù)分片等功能,

        根據(jù) XXL-JOB 官網(wǎng)介紹,其解決了很多 Quartz 的不足。

        XXL-JOB 的架構(gòu)設(shè)計(jì)如下圖所示:

        從上圖可以看出,XXL-JOB調(diào)度中心執(zhí)行器 兩大部分組成。調(diào)度中心主要負(fù)責(zé)任務(wù)管理、執(zhí)行器管理以及日志管理。執(zhí)行器主要是接收調(diào)度信號(hào)并處理。另外,調(diào)度中心進(jìn)行任務(wù)調(diào)度時(shí),是通過自研 RPC 來實(shí)現(xiàn)的。

        不同于 Elastic-Job 的去中心化設(shè)計(jì), XXL-JOB 的這種設(shè)計(jì)也被稱為中心化設(shè)計(jì)(調(diào)度中心調(diào)度多個(gè)執(zhí)行器執(zhí)行任務(wù))。

        Quzrtz 類似 XXL-JOB 也是基于數(shù)據(jù)庫鎖調(diào)度任務(wù),存在性能瓶頸。不過,一般在任務(wù)量不是特別大的情況下,沒有什么影響的,可以滿足絕大部分公司的要求。

        不要被 XXL-JOB 的架構(gòu)圖給嚇著了,實(shí)際上,我們要用 XXL-JOB 的話,只需要重寫 IJobHandler 自定義任務(wù)執(zhí)行邏輯就可以了,非常易用!

        @JobHandler(value="myApiJobHandler")
        @Component
        public?class?MyApiJobHandler?extends?IJobHandler?{

        ????@Override
        ????public?ReturnT?execute(String?param)?throws?Exception?{
        ????????//......
        ????????return?ReturnT.SUCCESS;
        ????}
        }

        還可以直接基于注解定義任務(wù)。

        @XxlJob("myAnnotationJobHandler")
        public?ReturnT?myAnnotationJobHandler(String?param)?throws?Exception?{
        ??//......
        ??return?ReturnT.SUCCESS;
        }

        相關(guān)地址:

        • Github 地址:https://github.com/xuxueli/xxl-job/。
        • 官方介紹:https://www.xuxueli.com/xxl-job/ 。

        優(yōu)缺點(diǎn)總結(jié):

        • 優(yōu)點(diǎn):開箱即用(學(xué)習(xí)成本比較低)、與 Spring 集成、支持分布式、支持集群、內(nèi)置了 UI 管理控制臺(tái)。
        • 缺點(diǎn):不支持動(dòng)態(tài)添加任務(wù)(如果一定想要?jiǎng)討B(tài)創(chuàng)建任務(wù)也是支持的,參見:xxl-job issue277)。

        PowerJob

        非常值得關(guān)注的一個(gè)分布式任務(wù)調(diào)度框架,分布式任務(wù)調(diào)度領(lǐng)域的新星。目前,已經(jīng)有很多公司接入比如 OPPO、京東、中通、思科。

        這個(gè)框架的誕生也挺有意思的,PowerJob 的作者當(dāng)時(shí)在阿里巴巴實(shí)習(xí)過,阿里巴巴那會(huì)使用的是內(nèi)部自研的 SchedulerX(阿里云付費(fèi)產(chǎn)品)。實(shí)習(xí)期滿之后,PowerJob 的作者離開了阿里巴巴。想著說自研一個(gè) SchedulerX,防止哪天 SchedulerX 滿足不了需求,于是 PowerJob 就誕生了。

        更多關(guān)于 PowerJob 的故事,小伙伴們可以去看看 PowerJob 作者的視頻 《我和我的任務(wù)調(diào)度中間件》。簡單點(diǎn)概括就是:“游戲沒啥意思了,我要扛起了新一代分布式任務(wù)調(diào)度與計(jì)算框架的大旗!”。

        由于 SchedulerX 屬于人民幣產(chǎn)品,我這里就不過多介紹。PowerJob 官方也對(duì)比過其和 QuartZ、XXL-JOB 以及 SchedulerX。

        總結(jié)

        這篇文章中,我主要介紹了:

        • 定時(shí)任務(wù)的相關(guān)概念 :為什么需要定時(shí)任務(wù)、定時(shí)任務(wù)中的核心角色、分布式定時(shí)任務(wù)。
        • 定時(shí)任務(wù)的技術(shù)選型 :XXL-JOB 2015 年推出,已經(jīng)經(jīng)過了很多年的考驗(yàn)。XXL-JOB 輕量級(jí),并且使用起來非常簡單。雖然存在性能瓶頸,但是,在絕大多數(shù)情況下,對(duì)于企業(yè)的基本需求來說是沒有影響的。PowerJob 屬于分布式任務(wù)調(diào)度領(lǐng)域里的新星,其穩(wěn)定性還有待繼續(xù)考察。ElasticJob 由于在架構(gòu)設(shè)計(jì)上是基于 Zookeeper ,而 XXL-JOB 是基于數(shù)據(jù)庫,性能方面的話,ElasticJob 略勝一籌。

        這篇文章并沒有介紹到實(shí)際使用,但是,并不代表實(shí)際使用不重要。我在寫這篇文章之前,已經(jīng)動(dòng)手寫過相應(yīng)的 Demo。如果你并沒有實(shí)際使用某個(gè)框架,就直接說它并不好用的話,是站不住腳的。

        程序汪資料鏈接

        程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

        Java項(xiàng)目分享 ?最新整理全集,找項(xiàng)目不累啦 04版

        堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階

        臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開放下載!

        臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

        字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開放下載!


        歡迎添加程序汪個(gè)人微信 itwang009? 進(jìn)粉絲群或圍觀朋友圈

        瀏覽 51
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            激情亚洲| 欧美日韩卡一卡二在线播放视频 | 老女人性爱视频在线观看 | 久操欧美 | 美女啪啪视频 | 永久中文字幕 | 日韩三级短视频 | 国产精品久久久久久久久久齐齐 | 啊轻点灬太粗太长了视频 | 毛片一区 |