前言
從事開發工作的同學,應該對定時任務的概念并不陌生,就是我們的系統在運行過程中能夠自動執行的一些任務、工作流程,無需人工干預。常見的使用場景包括:數據庫的定時備份、文件系統的定時上傳云端服務、每天早上的業務報表數據生成等等,實現方式也是層出不窮,對于簡單的任務,我們可能在代碼里寫個死循環一直判斷是否觸發執行即可,對于復雜的、或者定時任務的種類數量較多,需要嚴密監控管理的業務場景,我們需要一些專門的定時任務工具去處理。筆者用過的定時任務有三個,分別是QuartZ,XXL-JOB,Snail-Job,關于這三個工具,我做了一個表格用來區分其使用場景和特點。
對比維度 | Quartz | XXL-JOB | |
---|---|---|---|
項目定位 | 經典的 Java 定時任務框架,專注于任務調度核心能力 | 分布式任務調度平臺,強調易用性和企業級特性 | 輕量級分布式任務調度框架,注重性能和簡潔性 |
核心架構 | 單機 / 集群模式(基于數據庫鎖實現集群) | 中心化架構(調度中心 + 執行器) | 去中心化架構(無單獨調度中心,節點對等) |
任務觸發方式 | 基于 Cron 表達式、固定間隔、日歷等 | 支持 Cron 表達式、固定間隔、任務依賴、API 觸發 | 支持 Cron 表達式、固定間隔、事件觸發 |
分布式能力 | 需手動配置集群(依賴數據庫同步狀態) | 原生支持分布式部署,自動負載均衡 | 原生分布式,節點動態發現,自動容錯 |
任務管理 | 需通過代碼配置,無可視化界面 | 提供 Web 控制臺,支持任務 CRUD、啟停、日志查看 | 輕量控制臺,支持基礎任務管理和監控 |
失敗重試 | 支持簡單重試策略(需手動配置) | 內置失敗重試機制,可配置重試次數和間隔 | 支持自定義重試策略,默認失敗告警 |
任務依賴 | 不直接支持,需手動實現依賴邏輯 | 支持任務鏈式依賴、父子任務關系 | 支持簡單任務依賴,基于事件驅動 |
監控告警 | 無內置監控,需集成第三方工具(如 Prometheus) | 內置監控面板,支持郵件、釘釘等告警方式 | 支持指標暴露(適配 Prometheus),告警集成 |
性能表現 | 單機性能穩定,集群模式下受數據庫性能限制 | 調度中心性能優異,支持高并發任務觸發 | 去中心化設計,減少網絡開銷,性能高效 |
學習成本 | 中等(需理解 Job、Trigger、Scheduler 等核心概念) | 低(文檔豐富,開箱即用,API 簡潔) | 低(設計簡潔,源碼輕量,易于上手) |
適用場景 | 中小型應用、非分布式環境,或作為底層調度組件 | 中大型分布式系統、企業級應用,需可視化管理 | 微服務架構、輕量級應用、對性能敏感的場景 |
生態集成 | 可集成 Spring、Spring Boot 等框架 | 深度集成 Spring 生態,支持 Docker、K8s 部署 | 支持 Spring Boot 自動配置,適配主流框架 |
社區活躍度 | 老牌項目,社區成熟但更新較慢 | 社區活躍,更新頻繁,問題響應及時 | 新興項目,社區正在成長,維護積極 |
-
Quartz:作為定時任務領域的 “老前輩”,其核心優勢在于穩定性和靈活性,適合作為底層調度引擎,但在分布式場景下需要額外開發適配邏輯,更適合傳統單體應用或對定制化要求高的場景。
-
XXL-JOB:目前國內使用最廣泛的分布式任務調度平臺之一,憑借完善的功能和易用性成為企業級首選,尤其適合需要可視化管理、復雜任務依賴和高可靠性的場景(如電商定時任務、數據同步等)。
-
Snail-Job:作為新興框架,主打輕量和性能,去中心化設計減少了單點故障風險,適合微服務、云原生環境,或對部署復雜度和資源占用有嚴格要求的場景。
前兩種筆者都在實際開發中使用過,相比Quartz,XXL-JOB功能更為強大,但是也是比較重了,所以我在網上尋找有沒有類似的替代工具。哎還真讓我找到了,就是snail-job,輕量級的分布式任務重試、任務調度平臺,特別適合微服務項目,而且界面清爽顏值不錯,簡單好用,沒有復雜配置,接下來的內容我會逐步介紹如何在我們的微服務項目種集成它。不過我在這里需要聲明的一點是,企業級的大型項目,還是使用XXL-JOB,畢竟社區活躍,有很多技術支持,snail-job的很多技術問題,是需要付費才能獲得解答,這一點讓筆者不太舒服,而且開源也只開了一半,前端的代碼給的是打包編譯后的文件,不過一般的項目用著還是可以的。話不多說,直接上集成教程。
一、snail-job簡介
snail-job的官網地址:官網
上面的是它的官網界面截圖,snail-job分為服務端、客戶端兩組概念,這里簡單介紹下
- 服務端:用于管理和配置定時任務、監控定時任務的執行、告警配置等,總的來說就是定時任務的統籌調度中心
- 客戶端:這個是需要我們自己開發的部分,開發語言目前支持Python、Java,后續會支持其他語言,比如Go語言,主要是我們用來寫定時任務的執行邏輯的。
二、端口概念介紹
先介紹下snail-job集成涉及到的四個端口的概念,防止混淆不清
1.服務端應用端口:這個端口是后臺服務端的管理web頁面的入口端口,這個是可以自由配置修改的,snail-job的服務端和XXL-JOB一樣有一個管理頁面。
2. 服務端通訊端口:服務端對客戶端暴露的用來通信的端口,默認是17888,可以修改。
3. 客戶端應用端口:這是我們自己開發的客戶端服務的應用端口,也是自定義配置的。
4. 客戶端通訊端口:這個是我們開發的客戶端對服務端暴露的進行通信的端口,這個官網給的配置默認值是1789,當然支持修改。
之所以是高性能通信,因為其內部使用了netty框架,除了應用端口,單獨再搞兩個通信端口,一是為了通信不被干擾,二是為了通信數據的安全考慮,可以使用各種加密技術。本質是將 “用戶交互層” 和 “核心調度層” 解耦,在功能、協議、性能、安全等層面實現精細化管控,最終保障分布式任務調度的高效、穩定和可維護性。這一設計在主流分布式框架(如 XXL-Job、Dubbo)中也較為常見,是分布式系統架構的典型優化思路。
三、服務端集成部署
代碼拉取地址如下
# Gitee
git clone https://gitee.com/aizuda/snail-job.git# GitHub
git clone https://github.com/aizuda/snail-job.git
國內用戶直接使用gitee地址拉取就行,項目結構如下
項目拉下來后,筆者因為要和自己的微服務集成,所以對源碼做了一些簡單的修改,比如docker文件夾,就是我新加的,docker相關配置內容,dockerfile等。
還有就是,我把這個后端的服務也集成了nacos,使得能成功注冊到nacos中去,以便能和客戶端通信。下面是具體操作步驟。
引入nacos
如上圖所示,在源碼的相關路徑下引入nacos依賴,一個是注冊中心配置,一個是配置中心,這里的配置中心我其實是沒有使用了,即只是把服務注冊到了nacos上
<!-- SpringCloud Alibaba Nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2023.0.3.2</version></dependency><!-- SpringCloud Alibaba Nacos Config --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>2023.0.3.2</version></dependency>
application.yml文件修改
server:port: 9082servlet:context-path: /snail-jobspring:application:name: snail-job-serverprofiles:active: prodcloud:nacos:# 注冊中心discovery:server-addr: ****:8848namespace: hulei-devgroup: DEFAULT_GROUPusername: ******password: ******# 配置中心config:import-check:enabled: falseserver-addr: ****:8848namespace: hulei-devgroup: DEFAULT_GROUPusername: ******password: ******file-extension: ymldatasource:name: snail_job## mysqldriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/snail_job?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghaiusername: rootpassword: root
# url: jdbc:mysql://usteu-it.rwlb.rds.aliyuncs.com:3306/snail_job?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=Asia/Shanghai
# username: usteu_dev
# password: KDNT_dev999## postgres# driver-class-name: org.postgresql.Driver# url: jdbc:postgresql://localhost:5432/snail_job?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true# username: root# password: root## Oracle# driver-class-name: oracle.jdbc.OracleDriver# url: jdbc:oracle:thin:@//localhost:1521/XEPDB1# username: snail_job# password: SnailJob## SQL Server# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver# url: jdbc:sqlserver://localhost:1433;DatabaseName=snail_job;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true# username: SA# password: SnailJob@24## mariadb# driver-class-name: org.mariadb.jdbc.Driver# url: jdbc:mariadb://localhost:3308/snail_job?useSSL=false&characterEncoding=utf8&useUnicode=true# username: root# password: root## dm8# driver-class-name: dm.jdbc.driver.DmDriver# url: jdbc:dm://127.0.0.1:5236# username: SYSDBA# password: SYSDBA001# kingbase# driver-class-name: com.kingbase8.Driver# url: jdbc:kingbase8://localhost:54321/test# username: root# password: roottype: com.zaxxer.hikari.HikariDataSourcehikari:connection-timeout: 30000minimum-idle: 5maximum-pool-size: 100auto-commit: trueidle-timeout: 30000pool-name: snail_jobmax-lifetime: 1800000web:resources:static-locations: classpath:admin/mybatis-plus:typeAliasesPackage: com.aizuda.snailjob.template.datasource.persistence.poglobal-config:db-config:where-strategy: NOT_EMPTYcapital-mode: falselogic-delete-value: 1logic-not-delete-value: 0configuration:map-underscore-to-camel-case: truecache-enabled: truelogging:config: classpath:logback-boot.xml
# level:
# ## 方便調試 SQL
# com.aizuda.snailjob.template.datasource.persistence.mapper: debugsnail-job:retry-pull-page-size: 1000 # 拉取重試數據的每批次的大小job-pull-page-size: 1000 # 拉取重試數據的每批次的大小server-port: 17888 # 服務器端口log-storage: 7 # 日志保存時間(單位: day)rpc-type: grpc
從上面的yml文件可以看到,我的服務端的應用端口配置的是9082這個端口,關于服務端的通信端口,在如上文件底部所示為17888,這個是默認的,一般沒必要不去修改它。
服務端使用的數據庫,我用的是mysql,所以我注釋了其他數據庫的連接配置。一些建表的sql腳本自然也就是mysql相關的,sql腳本位置如下
把對應的sql腳本拷貝到,application.yml中,你配置的mysql數據庫中執行即可,執行后的自動創建的數據庫名稱就叫snail-job
關于docker文件,我沒有使用項目自帶的doc/docker下的任何內容,而是我直接在項目根目錄下創建了一個文件夾docker,里面配置了我自己的dockerfile,內容如下,給大家做個參考
# 基礎鏡像
FROM openjdk:17-jdk
# author
MAINTAINER usteuENV JAVA_OPTS="-Xms1g -Xmx2g -XX:+UseG1GC"# 掛載目錄
VOLUME /home/snailjob-server
# 創建目錄
RUN mkdir -p /home/snailjob-server
# 指定路徑
WORKDIR /home/snailjob-server
# 復制jar文件到路徑
COPY ./jar/snail-job-server-exec.jar /home/snailjob-server/snail-job-server-exec.jar
# 啟動系統服務
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar snail-job-server-exec.jar","-Duser.timezone=GMT+08"]
docker-compose.yml中有關snail-job服務端的service配置內容如下,可以看到,我映射了宿主機和容器的兩個端口
#snail-job的服務端docker部署,使用的是本地拉取的源碼打包部署,非鏡像snail-job-server:container_name: snail-job-serverbuild:context: ./snailjobServerdockerfile: dockerfileports:- "9082:9082"- "17888:17888"volumes:- /etc/localtime:/etc/localtime:ro# 添加日志限制,也可以全局限制,和services同級logging:driver: "json-file"options:max-size: "100m"max-file: "1"
讀者朋友也可以不用docker部署,這完全看個人需求,核心是snail-job-server-exec.jar這個jar包,至于怎么部署使用隨便。
snail-job-server-exec.jar
這個就是我們package后的可執行jar包了,里面包含服務端的前端頁面內容,具體位置如下
筆者使用的工具是IDEA,打包時操作如下圖所示,注意一定要勾選skipFrontend,否則就會打包失敗,前面說了前端并沒有給源碼,對此筆者也無力吐槽,畢竟人家搞出來這個東西就是為了掙錢的,免費給你用也還算可以了,要求不要太高哈。
服務端啟動登錄
對面上面的配置,使用IDEA啟動后登錄界面如下
瀏覽器訪問地址:http://localhost:9082/snail-job
默認登錄用戶admin,密碼也是admin,可以修改密碼,也可以新增普通用戶進行權限管控,這個后面再說,先登錄進去
可以看到一個在線機器,為服務端機器,同時nacos服務也多了一個snail-job-server的服務
新建命名空間
這個只有admin賬戶或者擁有管理員權限的賬戶才能新建,有一個默認的default,但我不想用,所以自己建了,名字什么的更專業點。
新建執行器組
上面選擇新建的命名空間,然后新增執行器組,這里的token后面客戶端會用到,比較重要
新建用戶
這個比較簡單了,就是新建用戶,賦予對應執行器組的權限,也可以對admin用戶密碼進行修改,不再敘述。
告警通知配置
這個主要是用來針對定時任務執行異常時的通知配置,可以配置通知人,通知方式等,實際使用中一般是,針對某個組的某個定時任務去配置,設置對應的通知人。
定時任務配置
這個是核心了,支持按照固定時間間隔、cron表達式、指定時間點、工作流方式,具體請看官方文檔介紹,十分詳細了。
任務類型一般就是默認集群。集群模式下,一個任務會根據路由策略只打到一個節點上執行。
執行器選擇自定義執行器,里面的名稱是我們在客戶端代碼些定時任務邏輯時對應的,不是亂填的。
四、客戶端開發部署
這部分內容需要我們自己在應用內開發了,也非常簡單,就拿筆者的微服務項目來展示吧
創建一個snail-job的客戶端微服務,也是要注冊到nacos當中去的,pom文件主要內容如下
<!-- SpringCloud Alibaba Nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- SpringCloud Alibaba Nacos Config --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--snail-job相關組件--><dependency><groupId>com.aizuda</groupId><artifactId>snail-job-client-starter</artifactId><version>${snail-job.version}</version></dependency><dependency><groupId>com.aizuda</groupId><artifactId>snail-job-client-job-core</artifactId><version>${snail-job.version}</version></dependency><dependency><groupId>com.aizuda</groupId><artifactId>snail-job-client-retry-core</artifactId><version>${snail-job.version}</version></dependency>
上面只是列舉的必要的依賴,可能還需要其他依賴,比如數據庫驅動等依賴。這個服務就是客戶端服務了,客戶端的應用端口我設置成了9081,yml配置文件如下
server:port: 9081spring:application:name: usteu-snailjobprofiles:active: @activatedProperties@cloud:nacos:# 注冊中心discovery:server-addr: @nacosAddress@namespace: @nacosNamespace@group: DEFAULT_GROUPusername: nacospassword: KDNT_dev666# 配置中心config:server-addr: @nacosAddress@namespace: @nacosNamespace@group: DEFAULT_GROUPusername: nacospassword: KDNT_dev666file-extension: ymlshared-configs:- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
其中還有部分配置,我是放在nacos中了,關鍵配置如下:
snail-job:# 任務調度服務器信息server:# 服務器IP地址(或域名);集群時建議通過 nginx 做負載均衡host: 127.0.0.1# 服務器通訊端口(不是后臺管理頁面服務端口)port: 17888# 命名空間 【上面配置的空間的唯一標識】namespace: vL4pfYMz8vFf5m0PXItAVK_aA9pGpH6L# 接入組名【上面配置的組名稱】注意: 若通過注解配置了這里的配置不生效group: usteu_group# 接入組 token 【上面配置的token信息】token: SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj# 客戶端綁定IP,必須服務器可以訪問到;默認自動推斷,在服務器無法調度客戶端時需要手動配置host: 127.0.0.1# 指定客戶端通訊端口,不配置默認 1789port: 1789# 是否開啟enabled: true
這里客戶端配置也是需要寫上服務端的ip地址和通信端口號的,之前說過是17888,剩下的命名空間,組名稱,以及執行器組對應的token,都可以登錄服務端管理界面去查找,客戶端的通訊端口,筆者這里配置的是1789,默認值也是這個。
定時任務編寫
我根據官方提供的案例,寫了一個測試的定時任務
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.client.model.ExecuteResult;
import com.aizuda.snailjob.common.log.SnailJobLog;
import org.springframework.stereotype.Component;/*** 測試定時任務*/
@Component
@JobExecutor(name = "testJob")
public class TestJob {public ExecuteResult jobExecute(JobArgs jobArgs) {SnailJobLog.REMOTE.info("哈哈,測試成功了:"+jobArgs.getJobId() + " " + jobArgs.getJobParams());return ExecuteResult.success();}
}
這個@JobExecutor(name = “testJob”)就是核心注解了,testJob即為我們在服務端進行定時配置時設置的執行器名稱,要對應上。
另外要注意的點是,再定義其他定時器時,方法名不要修改統一都是jobExecute,否則會執行報錯,我估計是反射時會尋找這個固定的執行方法吧。具體的定時任務執行業務邏輯,在方法內部寫即可。
如果你真想改的話,按照下面的方式改(我是覺得沒有什么必要改):
@JobExecutor(name = "testJob",method=自定義方法名)
附上一張測試結果圖
五、總結
以上內容簡單介紹了snail-job的使用方法和集成步驟,具體細節,如有不明白的,可參考官方文檔,學習測試。希望本文可以給各位帶來幫助。