叢集及分散式定時任務中介軟體MEE_TIMED
轉載請著名出處:https://www.cnblogs.com/funnyzpc/p/18312521
MEE_TIMED
一套開源的定時任務中介軟體,MEE_TIMED
簡化了 scheduled
及shedlock
的配置,同時也升級了這兩種中介軟體的能力 ,使定時任務開發更具靈活性的同時
具備叢集及分散式節點的管理,同時也增加了傳參,使之更加強大💪
開發初衷
目前 java
語言下可用的定時任務基礎元件無非這倆: spring scheduled
以及 quartz
,其中 scheduled
屬於輕量級的設計 預設整合在 spring-context
包中,所以springboot
使用 scheduled
簡單快捷,
既然簡單也必有簡單的侷限(後面會聊),quartz
則屬於重量級的設計,內部提供了 RMI
及 JMX
支援 以及使用基於DB的行鎖使之支援叢集,這都很好,不過內部程式碼設計及擴充套件似乎過於臃腫,不使用表又會退化為 scheduled
~
有時,專案不大不小,但是有叢集需求並且需要保證任務不重複執行,這時就需要 scheduled
+shedlock
這樣的搭配,可這樣無法動態傳參,同時增加了業務程式碼的複雜度,這是問題;
當然也可以使用 quartz
+資料庫表
的方式 則管理叢集及節點任務會變得比較複雜, 而且任務的啟停及關閉操作在分散式環境下使用 quartz
提供的api操作尤其的麻煩,這也是問題...
-
spring scheduled
所面臨的問題:CRON
表示式過於簡單,不支援複雜的表示式,比如每月最後一天,雖然提供zone
支援但在特殊的國度,如在美國,無法計算夏令時及冬令時的偏差- 當
@Schedules
與@SchedulerLock
配合時 多執行時間 會存在被鎖定的問題 scheduled
如果不指定執行緒池時 預設是單執行緒執行,不管應用下有多少定時任務都會是單執行緒,這是瓶頸...scheduled
不支援傳參,函式使用時必須是void
的函式返回且不可有形參- 部分api可能存在
spring
版本迭代時不相容問題,這是二開可能的問題
-
shedlock
的不足之處:- 無法做叢集及分散式節點管理,除非key定義的十分小心
- 不太好透過鎖的控制做任務及節點的啟停控制(可以透過特殊方法 比較另類)
- 任務執行時的關鍵資訊預設不記錄(IP、時間、CRON、應用資訊等等)
- 加鎖過程可能存在不必要的更新操作(這是程式碼問題)
基於現有情況我改造了 scheduled
,用較少的更改 做出了處於 scheduled
及 quartz
中間的定時任務元件,這就是 MEE_TIMED
🌹.
MEE_TIMED
所做的改進
- 新增
app
表(SYS_SHEDLOCK_APP
),提供叢集及多節點控制支援 - 擴充套件
job
(SYS_SHEDLOCK_JOB
)表data
欄位,提供傳參及引數修改支援 @Schedule
與@SchedulerLock
二合一並簡化註解配置spring scheduled
的CronExpression
替換為quartz
的CronExpression
,支援更靈活更復雜的CRON
表示式- 修改掉
scheduled
內部預設單執行緒的問題,提供執行緒池支援 - 固定於spring強繫結的api,儘量與
springboot
相容性做到最佳 - 任務資訊落表 等等
基本使用
詳細配置程式碼及後臺整合在mee-admin有例項 👊(,)👊
-
1.下載 表結構 及 mee_timed-X.X.X.jar 依賴 依賴 並存放於專案或nexus私服中
-
2.POM中定義dependency依賴:
<dependency> <groupId>com.mee.timed</groupId> <artifactId>mee_timed</artifactId> <version>1.0.1</version> <scope>system</scope> <systemPath>${pom.basedir}/src/main/resources/lib/mee_timed-1.0.1.jar</systemPath> </dependency>
-
3.匯入表結構(SQL)
根據所使用的
db
,按需匯入對應廠商所支援的表結構,目前僅提供mysql
、oracle
、postgresql
支援:table_mysql.sql table_oracle.sql table_postgresql.sql
-
4.定義配置及bean
目前配置僅有三項:
spring.mee.timed.shed=${spring.application.name} spring.mee.timed.table-name=SYS_SHEDLOCK_JOB spring.mee.timed.table-app-name=SYS_SHEDLOCK_APP
其中配置項
spring.mee.timed.table-app-name
是管理叢集及節點用的,如不需要可不配置
應用啟動時會自動寫入必要的初始化引數,也可提前將初始資料提前匯入配置bean: 這一步是非必須的,只是內部執行緒池的配置較為保守,如需自定義可以以下配置指定執行緒數及執行緒名字首:
/** * 設定執行執行緒數 * @return */ @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(PROCESSOR*2); scheduler.setThreadNamePrefix("SHEDLOCK-"); scheduler.initialize(); return scheduler; }
-
5.定義定時任務
樣例一:
import com.mee.timed.Job; import com.mee.timed.JobExecutionContext; import com.mee.timed.annotation.MeeTimed; import com.mee.timed.annotation.MeeTimeds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component public class Job01TestService implements Job { private static final Logger LOGGER = LoggerFactory.getLogger(Job01TestService.class); @MeeTimed(fixedRate = 10000,lockAtLeastFor = "PT5S",lockAtMostFor ="PT5S" ) public void exec01() throws InterruptedException { LOGGER.info("=====> [exec01] Already Executed! <====="); TimeUnit.SECONDS.sleep(6); } @MeeTimeds({ @MeeTimed(cron = "10,20,30,40,50 * * * * ?",lockAtMostFor ="PT5S",lockName = "execute1"), @MeeTimed(cron = "0 0/2 * * * ?",lockAtMostFor ="PT1M",lockName = "execute2"), @MeeTimed(cron = "0 0/4 * ? * MON-FRI",lockAtMostFor ="PT1M",lockName = "execute3"), // 紐約時間每年的7月9號22點2分執行 @MeeTimed(cron = "0 2 22 9 7 ?",lockAtMostFor ="PT1M",lockName = "execute4",zone = "America/New_York"), // 每月最後一天的十點半(eg:2024-07-31 10:30:00) @MeeTimed(cron = "0 30 10 L * ?",lockAtMostFor ="PT1M",lockName = "execute5") }) @Override public void execute(JobExecutionContext context) { LOGGER.info("=====> proxy job exec! data:"+context.getJobInfo().getName()+" <====="); try { TimeUnit.SECONDS.sleep(8); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
樣例二:
package com.mee.timed.test.job; import com.mee.timed.annotation.MeeTimed; import com.mee.timed.annotation.MeeTimeds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component public class ScheduledTasks { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTasks.class); @MeeTimeds({ @MeeTimed(fixedRate = 10000,lockAtLeastFor = "PT5S",lockAtMostFor ="PT5S",lockName = "T1"), @MeeTimed(fixedDelay = 8000,lockAtLeastFor = "PT5S",lockAtMostFor ="PT5S",lockName = "T2"), }) public void exec01() { LOGGER.info("=====> [exec01] Already Executed! <====="); } @MeeTimed(cron = "0/20 * * * * ?",lockAtLeastFor = "PT5S",lockAtMostFor ="PT10S" ) public void exec02(JobExecutionContext context) { LOGGER.info("=====> proxy job exec! data:"+context.getJobDataJson()+" <====="); } }
以上兩種方式均可,如果需要傳遞引數 其函式的形引數 必須是
JobExecutionContext
或其實現類如果是同一函式多時間配置(使用
@MeeTimeds
配置),其每一項lockName
不可為空!
整合後臺管理
-
具體效果及程式碼整合 具體見: mee-admin
-
後臺配置及管理
實際執行效果
2024-07-18 09:59:20.006 -> [MEE_TIMED-7] -> INFO com.mee.cron.JobTimedService:25 - =====> proxy job exec! data:{"key":"執行資料"} <=====
2024-07-18 09:59:40.020 -> [MEE_TIMED-7] -> INFO com.mee.cron.JobTimedService:25 - =====> proxy job exec! data:{"key":"執行資料"} <=====
2024-07-18 09:59:59.993 -> [MEE_TIMED-1] -> INFO com.mee.cron.DefaultTimerService:27 - ===>testTask2執行時間: 2024-07-18 09:59:59
2024-07-18 10:00:00.003 -> [MEE_TIMED-5] -> INFO com.mee.cron.DefaultTimerService:21 - ===>testTask1執行時間: 2024-07-18 10:00:00
2024-07-18 10:00:00.009 -> [MEE_TIMED-4] -> INFO com.mee.cron.JobTimedService:25 - =====> proxy job exec! data:{"key":"執行資料"} <=====
2024-07-18 10:00:20.014 -> [MEE_TIMED-4] -> INFO com.mee.cron.JobTimedService:25 - =====> proxy job exec! data:{"key":"執行資料"} <=====
2024-07-18 10:00:40.015 -> [MEE_TIMED-4] -> INFO com.mee.cron.JobTimedService:25 - =====> proxy job exec! data:{"key":"執行資料"} <=====
2024-07-18 10:01:00.019 -> [MEE_TIMED-4] -> INFO com.mee.cron.JobTimedService:25 - =====> proxy job exec! data:{"key":"執行資料"} <=====
後續計劃
-
首先是傳參考慮做反序列化處理,在必要場景下這是需要的
-
fix bug,當然這需要碼友多多支援啦
-
動態修改執行時間,尤其是
cron
,這功能是與quartz
的差距的縮小是決定性的 -
執行日誌支援,並提供擴充套件支援
-
其他待定
最後
再次感謝 spring scheduled
及 shedlock
的開源,MEE_TIMED
在 github
有開源,詳見: https://github.com/funnyzpc/mee_timed_parent 🎈