spring調(diào)度注解@Scheduled方式(含分布式)
簡(jiǎn)述
任務(wù)調(diào)度就是在給定的時(shí)間或固定頻率,執(zhí)行業(yè)務(wù)邏輯,是比較常見(jiàn)的功能需求。
解決方案有jdk原生的Timer、ScheduledThreadPoolExecutor等,這些類常適用于一些內(nèi)嵌的業(yè)務(wù)邏輯場(chǎng)景。
本文主要介紹注解@Scheduled,以上都是單進(jìn)程解決方案,經(jīng)過(guò)適當(dāng)改造,也可以適用于分布式場(chǎng)景,可以滿足大多數(shù)調(diào)度業(yè)務(wù)場(chǎng)景,具體實(shí)現(xiàn)思路下面會(huì)做簡(jiǎn)單敘述。
配置
開(kāi)啟
項(xiàng)目開(kāi)啟調(diào)度功能,需要先添加注解@EnableScheduling,否則調(diào)度注解@Scheduled就不起作用。
線程池
既然是任務(wù)運(yùn)行,就會(huì)涉及線程處理,如果有不同類型的任務(wù),也會(huì)出現(xiàn)并行處理,對(duì)線程的合理管理,就離不開(kāi)線程池,以下是線程池配置整理
(1) 不配置(默認(rèn))
如果不做任何配置處理,spring-boot 會(huì)默認(rèn)自動(dòng)構(gòu)建一個(gè)ThreadPoolTaskScheduler線程池類bean, 來(lái)管理這些運(yùn)行任務(wù)的線程,默認(rèn)線程池的具體參數(shù)值,可參考TaskSchedulingProperties類定義的默認(rèn)值,如下:
// pool private int size = 1; // thread private String threadNamePrefix = "scheduling-";
通過(guò)源碼知道,這個(gè)默認(rèn)線程池,內(nèi)部實(shí)際由jdk的ScheduledThreadPoolExecutor類處理,該類采用無(wú)限容量隊(duì)列,這也就限制了它的最大線程數(shù)不會(huì)超過(guò)1個(gè),如果有耗時(shí)的并行任務(wù),就不能滿足要求,通常情況下,需要根據(jù)業(yè)務(wù)場(chǎng)景重新配置這些參數(shù)。
(2) spring配置
spring-boot項(xiàng)目已提供TaskSchedulingAutoConfiguration類,由它自動(dòng)加載線程池配置參數(shù),并構(gòu)建ThreadPoolTaskScheduler線程池類bean,以下是約定的配置項(xiàng):
spring: task: scheduling: threadNamePrefix: my-scheduler-task- pool: size: 3
線程池的大小,依據(jù)配置調(diào)度注解@Scheduled任務(wù)的數(shù)量,原則上有幾種任務(wù)就需要幾個(gè)線程,否則就會(huì)出現(xiàn)相互影響,長(zhǎng)耗時(shí)任務(wù)占用線程,導(dǎo)致短耗時(shí)任務(wù)不能正常運(yùn)行。
(3) java代碼配置
調(diào)度任務(wù)不像@Async異常處理,它只有一個(gè)線程池,一般情況不用這種配置方式,以下是簡(jiǎn)單例子。
@Configuration public class ScheduleConfig { private static final String THREAD_NAME_PREFIX = "my-scheduler-task-"; @Bean("myTaskScheduler") public ThreadPoolTaskScheduler getThreadPoolTaskScheduler() { ThreadPoolTaskScheduler result = new ThreadPoolTaskScheduler(); result.setThreadNamePrefix(THREAD_NAME_PREFIX); result.setPoolSize(3); return result; } }
調(diào)度規(guī)則
@Scheduled包含參數(shù):
cron
:定時(shí)任務(wù),按cron表達(dá)式規(guī)則,定時(shí)運(yùn)行任務(wù),例如,每5分鐘運(yùn)行一次: 0/5 * * * * ?fixedDelay
:按固定間隔執(zhí)行,就是兩個(gè)相鄰任務(wù),前一個(gè)任務(wù)結(jié)束到下一個(gè)任務(wù)開(kāi)始的間隔時(shí)間,單位: 毫秒。fixedRate
:按固定頻率執(zhí)行任務(wù),單位: 毫秒。initialDelay
:系統(tǒng)啟動(dòng)后,延時(shí)多長(zhǎng)時(shí)間運(yùn)行第一次任務(wù),單位: 毫秒。
其中:cron, fixedDelay, fixedRate 配置參數(shù),只能三選一。
分布式
現(xiàn)在系統(tǒng)大多在分布式環(huán)境部署,就需要考慮多實(shí)例部署如何協(xié)調(diào)執(zhí)行任務(wù)問(wèn)題,以下是常見(jiàn)的解決方案,以及個(gè)人的思考。
第三方
目前第三方的開(kāi)源方案,有早期比較經(jīng)典的Quartz,近幾年版本迭代不太活躍,也有后起之秀XXL-JOB 版本迭代比較活躍,也是目前很多公司推崇的解決方案,對(duì)任務(wù)的管理、監(jiān)控、日志等功能比較齊全,可以參考其官方,這里就不再多述。
自處理
盡管上面開(kāi)源的第三方解決方案,已經(jīng)足夠成熟、完善,但相對(duì)來(lái)說(shuō),還是有些重,對(duì)于一些系統(tǒng)規(guī)模不是很大,一些簡(jiǎn)單的任務(wù)調(diào)度需求,完全可以進(jìn)行簡(jiǎn)單改造來(lái)滿足這些任務(wù)調(diào)度功能。
盡管簡(jiǎn)單,它一樣可以很實(shí)用、很健壯,以下是2種借助redis的處理思路。
(1) @Scheduled為主,redis為輔
通過(guò)@Scheduled注解的調(diào)度任務(wù),在分布式環(huán)境運(yùn)行,一個(gè)明顯的問(wèn)題,就是同一個(gè)任務(wù),可能會(huì)在多個(gè)機(jī)器同時(shí)并發(fā)執(zhí)行,如何避免,很自然就想到通過(guò)redis分布式鎖處理,來(lái)避免任務(wù)并發(fā)執(zhí)行,鎖定時(shí)間可以設(shè)置0.75個(gè)執(zhí)行周期,以下是偽碼:
@Scheduled(fixedDelay = 60000, initialDelay = 1000) public void task1() { // 鎖定 boolean isLock = redisLock.lock("my-task-1", 60000 * 0.75); if (!isLock) return; // 任務(wù)邏輯 doSomething(); }
可以看出,這種方式,任務(wù)周期誤差比較大,比較粗糙,特點(diǎn)就是邏輯簡(jiǎn)單,適用于精度要求較低的場(chǎng)景。
(2) redis為主,@Scheduled為輔
由于通過(guò)@Scheduled來(lái)配置執(zhí)行周期,在分布式環(huán)境,很難保證周期的精度,這時(shí)候可以把@Scheduled僅作為嘗試申請(qǐng)執(zhí)行的一個(gè)定時(shí)掃描任務(wù),真實(shí)的執(zhí)行周期由redis的過(guò)期時(shí)間來(lái)管理,這種方式,任務(wù)周期精度就會(huì)好很多,以下是偽碼:
按固定頻率執(zhí)行:
/* * redis為主,@Scheduled為輔(按固定頻率執(zhí)行任務(wù)) * * note: * a. @Scheduled注解中fixedDelay,該參數(shù)僅作為嘗試申請(qǐng)執(zhí)行任務(wù), 通??梢栽O(shè)置小些。 * b. 任務(wù)執(zhí)行周期或間隔,值為redisLock鎖定的時(shí)間。 * */ @Scheduled(fixedDelay = 5000, initialDelay = 1000) public void task2() { // 鎖定 boolean isLock = redisLock.lock("my-task-2", 真實(shí)任務(wù)周期); if (!isLock) return; // 任務(wù)邏輯 doSomething(); }
按固定間隔執(zhí)行:
/* * redis為主,@Scheduled為輔(按固定間隔執(zhí)行) * * note: * a. @Scheduled注解中fixedDelay,該參數(shù)僅作為嘗試申請(qǐng)執(zhí)行任務(wù), 通??梢栽O(shè)置小些。 * b. 任務(wù)執(zhí)行周期或間隔,值為redisLock鎖定的時(shí)間。 * */ @Scheduled(fixedDelay = 5000, initialDelay = 1000) public void task3() { // 鎖定1: 避免任務(wù)并行 boolean isLock = redisLock.lock("my-task-3", 真實(shí)任務(wù)間隔); if (!isLock) return; // 任務(wù)邏輯 doSomething(); // 鎖定2: 間隔時(shí)間 redisLock.expire("my-task-3", 真實(shí)任務(wù)間隔); }
按cron表達(dá)式執(zhí)行:可通過(guò)注解@Scheduled參數(shù)fixedDelay,來(lái)調(diào)整周期精度。
/* * redis為主,@Scheduled為輔(cron表達(dá)) * * note: * a. @Scheduled注解中fixedDelay,該參數(shù)僅作為嘗試申請(qǐng)執(zhí)行任務(wù), 通常可以設(shè)置小些。 * b. 任務(wù)執(zhí)行周期或間隔,值為redisLock鎖定的時(shí)間。 * c. 由CronHelper解析cron表達(dá)式,計(jì)算下一次運(yùn)行間隔時(shí)間 */ @Scheduled(fixedDelay = 5000, initialDelay = 1000) public void task4() { // 鎖定 boolean isLock = redisLock.lock("my-task-4", CronHelper.getNextDelayTime()); if (!isLock) return; // 任務(wù)邏輯 doSomething(); }
以上只偽碼,可以看出改造成本比較少,也足夠靈活,其中RedisLock可以參考前面整理的文章:分布式鎖-java,至于CronHelper類,網(wǎng)上應(yīng)該有類似資源,也不妨自己實(shí)現(xiàn)一下,應(yīng)該比排序算法有趣的多。
再就是任務(wù)的運(yùn)行,不能保證負(fù)載均衡,如果的確有這方面需求,通過(guò)redis隊(duì)列也可以實(shí)現(xiàn),邏輯也不會(huì)太復(fù)雜。
個(gè)人認(rèn)為:
這種自處理方式,借助redis還是可以保障它的高可用性、并發(fā)性能,它的主要缺陷,就是代碼語(yǔ)義不夠清晰,在維護(hù)上,容易受注解@Scheduled定時(shí)參數(shù)影響,實(shí)際業(yè)務(wù)場(chǎng)景,盡量封裝一下,提高可讀性。
常見(jiàn)問(wèn)題
(1) 線程池的大小,建議幾種任務(wù)就幾個(gè)線程,多了也浪費(fèi),如果太小,任務(wù)耗時(shí)長(zhǎng)時(shí),就會(huì)出現(xiàn)任務(wù)間干擾。
(2) 如果任務(wù)有嚴(yán)格的并行限制,可以通過(guò)分布式鎖防護(hù)一下。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot Redis實(shí)現(xiàn)接口冪等性校驗(yàn)方法詳細(xì)講解
這篇文章主要介紹了SpringBoot Redis實(shí)現(xiàn)接口冪等性校驗(yàn)方法,近期一個(gè)老項(xiàng)目出現(xiàn)了接口冪等性校驗(yàn)問(wèn)題,前端加了按鈕置灰,依然被人拉著接口參數(shù)一頓輸出,還是重復(fù)調(diào)用了接口,通過(guò)復(fù)制粘貼,完成了后端接口冪等性調(diào)用校驗(yàn)2022-11-11解決Nacos成功啟動(dòng)但是無(wú)法訪問(wèn) (Connection refused)
這篇文章主要介紹了解決Nacos成功啟動(dòng)但是無(wú)法訪問(wèn) (Connection refused)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06spring boot(三)之Spring Boot中Redis的使用
這篇文章主要介紹了spring boot(三)之Spring Boot中Redis的使用,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-05-05使用springboot實(shí)現(xiàn)上傳文件時(shí)校驗(yàn)文件是否有病毒
在SpringBoot中實(shí)現(xiàn)文件上傳時(shí)的病毒校驗(yàn),可以使用ClamAV、Metascan或VirusTotal等工具,這些工具通過(guò)掃描上傳的文件,可以有效地檢測(cè)和阻止惡意軟件的傳播,安裝和配置ClamAV服務(wù)的步驟如下:下載并安裝ClamAV二進(jìn)制文件,配置clamd.conf文件2025-01-01Spring?Cloud?Alibaba?Nacos兩種檢查機(jī)制
這篇文章主要介紹了Spring?Cloud?Alibaba?Nacos兩種檢查機(jī)制,作為注冊(cè)中心不止提供了服務(wù)注冊(cè)和服務(wù)發(fā)現(xiàn)功能,它還提供了服務(wù)可用性監(jiān)測(cè)的機(jī)制,下面我們就一起進(jìn)入文章了解具體詳情吧2022-05-05Java 時(shí)間日期詳細(xì)介紹及實(shí)例
這篇文章主要介紹了Java 時(shí)間日期詳細(xì)介紹及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-01-01關(guān)于synchronized的參數(shù)及其含義
這篇文章主要介紹了synchronized的參數(shù)及其含義詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10