爲什麼不推薦使用Spring @Scheduled 中的 Cron 表達式

原因:

       Spring @Scheduled 使用非常方便,你只需要在指定執行定時任務的方法上添加 @Scheduled 註釋即可,但是在最近用 @Scheduled 使用 Cron 表達式時,出現了大問題,問題如下:

       首先,時鐘往過去撥,@Scheduled 的 Cron 表達式不會按預期執行了。(原因見後續源碼分析)

       其次,Cron 不按預期執行了,Spring 也沒有提供接口進行重啓 Scheduled Job。

       最後,當系統龐大後,你無法追蹤每一個 @Scheduled,當出現 Scheduled Job 失效後,你只能重啓 JVM。

       造成 Scheduled Job 失效的原因,貼源碼 org.springframework.scheduling.concurrent.ReschedulingRunnable:

	@Nullable
	public ScheduledFuture<?> schedule() {
		synchronized (this.triggerContextMonitor) {
			this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
			if (this.scheduledExecutionTime == null) {
				return null;
			}
			long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
			this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
			return this;
		}
	}

       this.scheduledExecutionTime 是根據上一次執行的開始時間加上Cron 表達式的規則計算出來的,該時間爲下一次本應該執行的時間。

       System.currentTimeMillis(); 是Scheduled Job 失效的根本原因。看如下公式:

initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis()

       this.scheduledExecutionTime.getTime()  不變的情況下,System.currentTimeMillis() 往過去撥,那麼 initialDelay 就是變大;System.currentTimeMillis() 往未來撥,那麼 initialDelay 就會變小。

       因此,時鐘往未來撥動是沒有問題的,initialDelay 小於等於 0 , 線程會立馬執行;然而,時鐘若往過去撥動,initialDelay 就會變大,如果撥動一天,那麼 initialDelay 就大於一天。造成 Scheduled Job 短暫失效。

       當然,如果有方法修改該方法的化,我們應該對 initialDelay 的獲取進行一定的優化,如若 initialDelay 與 Cron 規則相差得太遠,那麼 this.scheduledExecutionTime 可以嘗試不基於上一次執行時間計算,而是基於當前系統時間計算而來。

      不僅是Spring 的 @Scheduled, Quartz 實現的原理也和 @Scheduled 一樣。

解決方法:

      Spring @Scheduled  的內部實現是對外封裝的,重寫上述方法不現實。因此可以用這位大神的 Cron4j 開源替代框架,該框架能解決時鐘的回撥問題。

      另外,如果你不想用該框架,也可以將所有的 Scheduled Job 集中管理,當 Scheduled Job 失效後,重啓所有的 Scheduled Job。這麼容易就重啓 Scheduled Job ?,當然,我認爲能用 Scheduled Job 做的工作都不應該是高精度的工作,如果要實現高精度的工作,則應該增加輔助實現方式或者使用其它方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章