Spring Boot 2.x基礎教程:使用Elastic Job實現定時任務


上一篇,我們介紹了如何使用Spring Boot自帶的@Scheduled注解實現定時任務。文末也提及了這種方式的局限性。當在集群環境下的時候,如果任務的執行或操作依賴一些共享資源的話,就會存在競爭關系。如果不引入分布式鎖等機制來做調度的話,就可能出現預料之外的執行結果。所以,@Scheduled注解更偏向於使用在單實例自身維護相關的一些定時任務上會更為合理一些,比如:定時清理服務實例某個目錄下的文件、定時上傳本實例的一些統計數據等。

那么,在實際實現業務邏輯的時候,沒有更好的定時任務方案呢?今天我們就來介紹一個老牌的分布式定時任務框架,在Spring Boot下的使用案例。

Elasitc Job

Elastic Job的前生是當當開源的一款分布式任務調度框架,而目前已經加入到了Apache基金會。

該項目下有兩個分支:ElasticJob-Lite和ElasticJob-Cloud。ElasticJob-Lite是一個輕量級的任務管理方案,本文接下來的案例就用這個來實現。而
ElasticJob-Cloud則相對重一些,因為它使用容器來管理任務和隔離資源。

更多關於ElasticJob的介紹,您也可以點擊這里直達官方網站了解更多信息。

動手試試

說那么多,一起動手試試吧!

第一步:創建一個最基礎的Spring Boot項目,如果還不會?那么看看這篇快速入門

第二步pom.xml中添加elasticjob-lite的starter

<dependencies>
    <dependency>
        <groupId>org.apache.shardingsphere.elasticjob</groupId>
        <artifactId>elasticjob-lite-spring-boot-starter</artifactId>
        <version>3.0.0</version>
    </dependency>

    // ...
</dependencies>

第三步:創建一個簡單任務

@Slf4j
@Service
public class MySimpleJob implements SimpleJob {

    @Override
    public void execute(ShardingContext context) {
        log.info("MySimpleJob start : didispace.com {}", System.currentTimeMillis());
    }

}

第四步:編輯配置文件

elasticjob.reg-center.server-lists=localhost:2181
elasticjob.reg-center.namespace=didispace

elasticjob.jobs.my-simple-job.elastic-job-class=com.didispace.chapter72.MySimpleJob
elasticjob.jobs.my-simple-job.cron=0/5 * * * * ?
elasticjob.jobs.my-simple-job.sharding-total-count=1

這里主要有兩個部分:

第一部分:elasticjob.reg-center開頭的,主要配置elastic job的注冊中心和namespace

第二部分:任務配置,以elasticjob.jobs開頭,這里的my-simple-job是任務的名稱,根據你的喜好命名即可,但不要重復。任務的下的配置elastic-job-class是任務的實現類,cron是執行規則表達式,sharding-total-count是任務分片的總數。我們可以通過這個參數來把任務切分,實現並行處理。這里先設置為1,后面我們另外講分片的使用。

運行與測試

完成了上面所有操作時候,我們可以嘗試運行一下上面應用,因為這里需要用到ZooKeeper來協調分布式環境下的任務調度。所以,你需要先在本地安裝ZooKeeper,然后啟動它。注意:上面elasticjob.reg-center.server-lists配置,根據你實際使用的ZooKeeper地址和端口做相應修改。

在啟動上述Spring Boot應用之后,我們可以看到如下日志輸出:

2021-07-20 15:33:39.541  INFO 56365 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler 'my-simple-job' initialized from an externally provided properties instance.
2021-07-20 15:33:39.541  INFO 56365 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler version: 2.3.2
2021-07-20 15:33:39.551  INFO 56365 --- [           main] org.apache.curator.utils.Compatibility   : Using org.apache.zookeeper.server.quorum.MultipleAddresses
2021-07-20 15:33:40.067  INFO 56365 --- [           main] c.d.chapter72.Chapter72Application       : Started Chapter72Application in 3.25 seconds (JVM running for 4.965)
2021-07-20 15:33:40.069  INFO 56365 --- [           main] .s.b.j.ScheduleJobBootstrapStartupRunner : Starting ElasticJob Bootstrap.
2021-07-20 15:33:40.078  INFO 56365 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler my-simple-job_$_NON_CLUSTERED started.
2021-07-20 15:33:40.078  INFO 56365 --- [           main] .s.b.j.ScheduleJobBootstrapStartupRunner : ElasticJob Bootstrap started.
2021-07-20 15:33:45.157  INFO 56365 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob      : MySimpleJob start : didispace.com 1626766425157
2021-07-20 15:33:50.010  INFO 56365 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob      : MySimpleJob start : didispace.com 1626766430010
2021-07-20 15:33:55.013  INFO 56365 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob      : MySimpleJob start : didispace.com 1626766435013

既然是分布式任務調度,那么我們再啟動一個(注意,在同一台機器啟動的時候,會端口沖突,可以在啟動命令中加入-Dserver.port=8081來區分端口),在第二個啟動的服務日志也打印了類似的內容

2021-07-20 15:34:06.430  INFO 56371 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler 'my-simple-job' initialized from an externally provided properties instance.
2021-07-20 15:34:06.430  INFO 56371 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler version: 2.3.2
2021-07-20 15:34:06.436  INFO 56371 --- [           main] org.apache.curator.utils.Compatibility   : Using org.apache.zookeeper.server.quorum.MultipleAddresses
2021-07-20 15:34:06.786  INFO 56371 --- [           main] c.d.chapter72.Chapter72Application       : Started Chapter72Application in 1.446 seconds (JVM running for 1.884)
2021-07-20 15:34:06.787  INFO 56371 --- [           main] .s.b.j.ScheduleJobBootstrapStartupRunner : Starting ElasticJob Bootstrap.
2021-07-20 15:34:06.792  INFO 56371 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler my-simple-job_$_NON_CLUSTERED started.
2021-07-20 15:34:06.792  INFO 56371 --- [           main] .s.b.j.ScheduleJobBootstrapStartupRunner : ElasticJob Bootstrap started.
2021-07-20 15:34:10.182  INFO 56371 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob      : MySimpleJob start : didispace.com 1626766450182
2021-07-20 15:34:15.010  INFO 56371 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob      : MySimpleJob start : didispace.com 1626766455010
2021-07-20 15:34:20.013  INFO 56371 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob      : MySimpleJob start : didispace.com 1626766460013

此時,在回頭看看之前第一個啟動的應用,日志輸出停止了。由於我們設置了分片總數為1,所以這個任務啟動之后,只會有一個實例接管執行。這樣就避免了多個進行同時重復的執行相同邏輯而產生問題的情況。同時,這樣也支持了任務執行的高可用。比如:可以嘗試把第二個啟動的應用(正在打印日志的)終止掉。可以發現,第一個啟動的應用(之前已經停止輸出日志)繼續開始打印任務日志了。

在整個實現過程中,我們並沒有自己手工的去編寫任何的分布式鎖等代碼去實現任務調度邏輯,只需要關注任務邏輯本身,然后通過配置分片的方式來控制任務的分割,就可以輕松的實現分布式集群環境下的定時任務管理了。是不是在復雜場景下,這種方式實現起來要比@Scheduled更方便呢?

記得自己動手寫一寫,這樣體會更深哦!如果碰到問題,可以拉取文末的代碼示例對比一下是否有地方配置不一樣。下一篇,我們還將繼續介紹關於定時任務的一些高級內容。如果您對這個內容感興趣,可以收藏本系列教程《Spring Boot 2.x基礎教程》點擊直達!。學習過程中如遇困難,可以加入我們的Spring技術交流群,參與交流與討論,更好的學習與進步!

代碼示例

本文的完整工程可以查看下面倉庫中的chapter7-2目錄:

如果您覺得本文不錯,歡迎Star支持,您的關注是我堅持的動力!

歡迎關注我的公眾號:程序猿DD,分享外面看不到的干貨與思考!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM