Spring定時任務為您在Java體系下定時任務開放提供了便捷方式,但其便捷的同時也有很多企業化場景下的局限性。通過對接SchedulerX任務調度可快速實現企業化運用的支持。
前提條件
客戶端1.7.10及以上版本
采用Spring Boot模式接入SchedulerX平臺具體操作,請參見Spring Boot應用接入SchedulerX。
接入指南
接入配置
以Spring Boot接入模式為例,應用程序的pom.xml文件中添加依賴及啟動類。
schedulerx2.version
使用客戶端最新版本。更多信息,請參見客戶端發布記錄。
<dependency>
<groupId>com.aliyun.schedulerx</groupId>
<artifactId>schedulerx2-spring-boot-starter</artifactId>
<version>${schedulerx2.version}</version>
<!--如果用的是logback,需要把log4j和log4j2排掉 -->
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
無論是已經使用Spring定時任務或初次使用,都需要在啟用類上保持@EnableScheduling
注解開啟。如下所示:
@SpringBootApplication
@EnableScheduling /** 開啟Spring定時任務 */
public class SchedulerXWorkerApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulerXWorkerApplication.class, args);
}
}
/** Spring原生配置的定時任務類*/
@Service
public class SpringScheduledProcessor {
@Scheduled(cron = "0/2 * * * * ?")
public void hello() {
logger.info(DateUtil.now() + " hello world. start");
logger.info(DateUtil.now() + " hello world. end");
}
}
對于新接入上述配置或已經滿足上述配置的用戶,SchedulerX默認不會主動接管業務應用中原本的Spring定時任務,相應定時任務還是會由Spring容器進行調度,不影響原本已有的Spring定時任務運行。
后續業務需要讓SchedulerX任務調度平臺來接管Spring定時任務的運行,則可以在Properties文件中添加如下配置。
# 啟用SchedulerX接管Spring定時任務
spring.schedulerx2.task.scheduling.scheduler=schedulerx
添加完成后,就可以通過下文中任務配置運行或任務自動同步進行任務管控配置和定時運行了。
任務配置運行
登錄分布式任務調度平臺。
在左側導航欄,單擊任務管理。
在任務管理頁面,單擊創建任務。選擇springschedule任務類型,配置對應定時任務類及其方法名。
配置名稱
意義
任務名
任務名稱。
描述
任務描述,盡量簡潔地描述業務,便于后續搜索。
應用ID
任務所屬分組。可以在下拉列表中選擇。
任務類型
指任務所實現的語言,當前支持Java、Shell、Python、Go、http、Node.js、xxljob和DataWorks類型,其中Shell、Python和Go會彈出編輯框,在編輯框中編寫任務腳本。
本文任務類型為springschedule。
spring schedule配置
定時任務代碼的完整類名(class name)和任務的方法名(method name)。
執行模式
執行模式,這里特指任務執行的模式,當前支持以下模式。
單機運行:隨機選一臺機器執行。
廣播運行:所有機器同時執行并等待全部結束。
說明當選擇了不同的執行模式后,高級設置中的參數會隨之變化。
優先級
同一應用下多個任務同時在一個實例中運行時,優先級高的任務會被優先執行。但當一個應用中的多個應用在多個實例中運行時,不同優先級的任務被調度到不同實例執行,可能導致低優先級任務被優先執行。SchedulerX通過可搶占的優先級隊列規避了這種可能性,并保證同時在池子中等待的高優先級任務被優先執行。更多信息,請參見可搶占的優先級隊列。
任務參數
任意字符串,可以在運行時通過上下文獲取。
配置對應定時觸發頻率。
說明頻率會以控制臺配置的頻率為準,Spring定時任務代碼中原生注解
@Scheduled
中配置將會失效,但該注解在代碼中需要保留。定時參數說明如下:
配置名稱
意義
時間類型
none:無調度方式,一般通過工作流觸發。
cron:Cron表達式。
api:通過API觸發。
fixed_rate:固定頻率。
second_delay:秒級固定延遲。
one_time:一次性任務。
cron表達式(僅適用于cron時間類型)
填寫Cron表達式。可以直接按照Cron語法填寫,也可以使用工具生成并驗證。
固定頻率(僅適用于fixed_rate時間類型)
填寫固定頻率,單位為秒,只支持60秒以上。例如200表示每200s調度一次。
固定延遲(僅適用于second_delay時間類型)
填寫固定延遲,單位為秒。范圍為1秒~60秒。例如5表示延遲5秒觸發調度。
高級配置參數說明如下:
配置名稱
意義
時間偏移
數據時間相對于調度時間的偏移,可以在調度時從上下文獲取該值。
時區
可以根據實際情況選擇不同時區,包括一些常用國家或地區,也包括標準的GMT表達方式。
設置相關報警條件和通知渠道等。關于通知渠道,請參見通知聯系人和通知聯系人組。
完成上述步驟后,SchedulerX任務調度平臺即可接管運行Spring的定時任務,支持為原生的Spring任務帶來的各種可視化管控、任務業務日志查詢、執行鏈路查看、任務執行通知報警等企業級能力。
任務自動同步
原本已使用Spring定時任務且存量定時任務較多的用戶,可以選擇在應用配置文件中開啟自動同步任務,大幅度簡化上述手動創建的過程。Properties配置參考如下。
# SchedulerX接管Spring定時任務,開啟自動同步任務
spring.schedulerx2.task.scheduling.sync=true
# 在開啟自動同步參數情況下,需額外配置以下參數。如果自動同步未開啟則如下參數可不配置。
spring.schedulerx2.regionId=同步至目標區域編號(關于RegionId請參見服務接入點)
spring.schedulerx2.aliyunAccessKey=XXXXXXXXX
spring.schedulerx2.aliyunSecretKey=XXXXXXXXX
部分地域的RegionId如下所示。更多信息,請參見服務接入點。
地域名稱 | 地域ID | 公網接入地址 | VPC接入地址 |
華東1(杭州) | cn-hangzhou | schedulerx.aliyuncs.com | schedulerx-vpc.cn-hangzhou.aliyuncs.com |
華東2(上海) | cn-shanghai | schedulerx.aliyuncs.com | schedulerx-vpc.cn-shanghai.aliyuncs.com |
公網 | public | schedulerx.aliyuncs.com | 無 |
自動同步的任務為了保持與原生Spring任務在集群環境中運行的規則一致,默認同步至平臺上的任務執行模式為廣播運行(即集群中每臺機器都會在相應時點執行該任務)。如果業務需要自動在集群機器中選擇一個運行,可在控制臺編輯相應任務執行模式為單機運行。關于參數的詳細信息,請參見任務配置運行。
常見問題
SchedulerX接管后原Spring定時器依舊運行
由于應用中配置了自定義的Scheduler調度器導致SchedulerX覆蓋自定義處理器。請排查業務應用工程中是否存在實現org.springframework.scheduling.annotation.SchedulingConfigurer
接口的類,確認是否調用了ScheduledTaskRegistrar的setScheduler方法覆蓋默認調度器。
Spring任務如何獲取任務上下文
在業務應用工程代碼中增加以下代碼獲取任務上下文。
JobContext jobContext = ContainerFactory.getContainerPool().getContext();
Spring任務是否支持返回結果
版本客戶端大于1.10.11時,Spring任務支持返回結果,您可直接在定時方法上返回任意結果。
@Scheduled(cron = "0/5 * * * * ?")
public ProcessResult helloStandalone1() {
try {
logger.info(DateUtil.now() + " " + Thread.currentThread().getName() + " hello world. start");
TimeUnit.SECONDS.sleep(2L);
logger.info(DateUtil.now() + " " + Thread.currentThread().getName() + " hello world. end");
} catch (Exception e) {
e.printStackTrace();
logger.info(DateUtil.now() + " " + Thread.currentThread().getName() + " hello world. exception end..");
}
return new ProcessResult(true, "執行結果信息");
}
@Scheduled(cron = "0/5 * * * * ?")
public String helloStandalone2() {
try {
logger.info(DateUtil.now() + " " + Thread.currentThread().getName() + " hello world. start");
TimeUnit.SECONDS.sleep(2L);
logger.info(DateUtil.now() + " " + Thread.currentThread().getName() + " hello world. end");
} catch (Exception e) {
e.printStackTrace();
logger.info(DateUtil.now() + " " + Thread.currentThread().getName() + " hello world. exception end..");
}
return "執行結果信息";
}