做爲業務開發人員,會常常須要寫一個定時任務。目前,寫定時任務應用最普遍最成熟的方案是OpenSymphony開源組織在任務調度領域的一個開源項目quartz,好比要寫一個定時數據同步任務,在完成quartz的相關配置後,只須要寫一個jobBean_A就能基於quartz完成調度;若是要再開發一個任務,那麼再寫一個jobBean_B。html
這樣作的方式有一個地方不是很好,quartz調度與job業務耦合在一塊兒,當任務數量愈來愈多,甚至達到成千上萬個,對於項目自己的維護也是一個挺大的挑戰。這時,會考慮將quartz任務項目拆分出來,但這樣每個任務工程都須要依賴quartz,不方便統一調度管理。爲了能統一管理調度任務,又能將調度和job業務分離,咱們提出雲調度方案。git
雲調度中心ferrari的設計目標:github
基於上述初衷,咱們提出的雲調度中心設計方案,如圖: web
總共分爲三層:調度控制層,調度接入層,業務層。spring
這樣,要開發一個任務,基本不用關心調度控制層和接入層的邏輯,只需在業務層實現任務邏輯便可。任務開發完後,在調度控制中心新增一個調度任務信息,即可接收調度中心的調度。數據庫
雲調度中心的實現是基於quartz,因此對quartz必須有個清楚的理解。apache
Quartz任務調度的核心元素是scheduler(調度器),trigger(觸發器,用於定義調度時間規則) 和 job(任務),其中 trigger 和 job 是任務調度的元數據,scheduler 是實際執行調度的控制器。quartz內部的調度原理能夠查看後面列舉的參考文檔,這裏具體講講使用quartz的幾個注意點。 ####3.1 線程池配置 quartz.properties裏的線程池配置:架構
#Configure ThreadPool org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 15 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
Quartz 中自帶了一個線程池的實現SimpleThreadPool,並經過threadCount來設置最大併發數,通常設置在10~50比較合適。 ####3.2 misfire策略 misfire,即錯過的,指原本應該被執行但實際沒有被執行的任務調度,通常來講引發misfire的狀況有如下4種:併發
quartz對misfire的產生有個時間條件,超過這個設定的時間則認爲是misfire,配置以下:app
#Misfire org.quartz.jobStore.misfireThreshold: 120000 #120秒 org.quartz.jobStore.maxMisfiresToHandleAtATime: 1
爲了處理 misfired job,Quartz 中爲 trigger 定義了處理策略,主要有下面兩種:
ferrari使用的是第2種策略:
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
####3.3 調度集羣設置 Quartz 中的 trigger 和 job 須要存儲下來才能被使用,有兩種存儲方式:內存和數據庫。若是將調度信息存儲在內存,那麼只要quartz應用重啓,這些信息就會丟失,因此在生產環境中,通常都用數據庫存儲的方式,配置以下:
#Configure JobStore for RAM #org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore #for cluster org.quartz.jobStore.tablePrefix = XXX_ org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.isClustered: true org.quartz.jobStore.clusterCheckinInterval: 20000
jobStore設置爲jdbcjobstore.JobStoreTX即表明數據庫存儲方式,因爲quartz集羣是經過數據表來實現併發鎖控制,因此須要設置集羣的節點檢查輪訓時間clusterCheckinInterval,這裏設置爲20s。 ####3.4 調度器配置 spring對quartz進行了整合,這裏採用基於spring的quartz配置,以下:
<!-- 默認 lazy-init="false"spring-context-support version: 3.2.14.RELEASE quartz version:2.2.2--> <bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 設置自動啓動 --> <property name="autoStartup" value="true" /> <!--必須的,QuartzScheduler 延時啓動,應用啓動完後 QuartzScheduler 再啓動 --> <property name="startupDelay" value="20" /> <!--須要overwrite已經存在的job,若是須要動態的修改已經存在的job,就須要設置爲true,不然會以數據庫中已經存在的爲準--> <property name="overwriteExistingJobs" value="true" /> <property name="applicationContextSchedulerContextKey" value="applicationContextKey" /> <property name="configLocation" value="classpath:quartz.properties"/> </bean>
####3.5 quartz任務開發 基於雲調度中心ferrari的架構,在調度控制中心層,咱們只要開發一個quartzJobBean。在這個jobBean中,基於ferrari協議將須要執行的類名、方法名、入參、任務機器地址等信息封裝成一個request,而後發送請求(ferrari用的是http請求)到任務目標機器。
在業務層,任務機器接收到調度控制中心的指令,解析出任務信息,便交給任務執行線程池,任務執行線程經過反射調用目標任務類進行執行。做爲業務層,無需基於quartz作任何開發,只需開發一個普通的類(稱爲任務類),而後將任務信息配置到調度控制中心,即可實現調度。
####4.1 maven依賴
<dependency> <groupId>com.dianping</groupId> <artifactId>ferrari-core</artifactId> <version>1.2.4</version> </dependency>
####4.2 web.xml配置servlet入口
<servlet> <servlet-name>FerrariServlet</servlet-name> <servlet-class>com.cip.ferrari.core.FerrariDirectServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>FerrariServlet</servlet-name> <url-pattern>/xxx/*</url-pattern> </servlet-mapping>
####4.3 開始寫你的任務類及方法,類名、方法、入參在新增任務時配置 ferrari新增任務界面: ####4.4 雲調度中心日誌接入 因爲job任務不在調度中心執行,而是有另外的job服務機器執行,因此要看業務日誌代碼,必須登陸對應的業務機器。若是job不少,又散落在各個機器,那麼要查看job運行日誌,效率就會比較低。爲了方便日誌查看,ferrari提供了日誌接入方案,在log4j.xml中增長一個append配置:
<appender name="FERRARI" class="com.cip.ferrari.core.log.FerrariFileAppender"> <param name="filePath" value="/data/applogs/xxx/"/> <param name="append" value="true"/> <param name="encoding" value="UTF-8"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%t]-[%M]-[%L]-[%p] %m%n"/> </layout> </appender>
其中,filePath 是日誌文件夾路徑。只要將日誌輸出在這個appender上,就能在調度控制中心遠程查看業務執行的日誌,如圖所示:
雲調度中心ferrari的實現源碼請移步: https://github.com/tkyuan/ferrari 記得給star^_^ ####參考文檔: