spring cloud 定時任務


項目中,因為使用了第三方支付(支付寶和微信支付),支付完畢后,第三方支付平台一般會采用異步回調通知的方式,通知商戶支付結果,然后商戶根據通知內容,變更商戶項目支付訂單的狀態。一般來說,為了防止商戶項目自身因為一些特殊原因,比如正好當時網絡狀態不穩定,商戶回調接口無法訪問,或者商戶回調接口出現異常。第三方支付平台,一般會發送多次請求來盡量確保通知到商戶系統。

但是,總會有各種各樣的情況,導致,第三方平台所有的通知次數通知完畢后,商戶系統依然沒有正確處理掉改筆訂單狀態。(一般第三方支付平台,會:1分鍾,10分鍾,1小時,6小時,12小時,24小時通知商戶支付接口,當然這個時間只是舉例而已,實際的通知時間會和這個不太一樣,但是也是大致的意思)。當如果按這個頻次通知完畢后,第三方支付平台就不再進行通知了。

所以,一旦第三方支付平台,將通知次數發送完畢后,平台依然沒有處理好自己項目的訂單狀態,那么訂單狀態很可能是,客戶實際已經完成支付,但是自己項目這邊依然顯示:“等待支付”這樣的狀態。那么為了解決這類問題,一般,我們會采用定時任務的方式,比如1個小時,我們把訂單支付已經超過1小時,但是還沒超過24小時的訂單去第三方平台主動的查詢一次訂單狀態。避免因為自己原因,導致的訂單狀態不一致的情況。這類設置已經超過1小時,未超過24小時的原因,主要是為了查詢效率,因為半小時之內還未支付,我們認為實際中存在這個可能性,所以可以等到1個小時,在查狀態,而不超過24小時的原因是,一旦超過24小時,我們可以認為訂單實際上已經超時失敗了,如果查這個訂單,就會導致,每次定時任務都會把之前已經查過的訂單,在反復的查詢。造成大量不必要的查詢。一般我們也會在啟一個定時任務,24小時執行一次,把一些訂單重置為失敗。減少不必要的失敗訂單的反復查詢。

下面進入正題,在spring boot中,如何來處理定時任務。 默認,springboot已經支持了定時任務Schedule模塊,所以一般情況已經完全能夠滿足我們的實際需求,一般來說,沒有必要在加入其他類似於:quartz

另外,在這里提一個實際項目中,關於定時任務的架構上的一些考慮:

一般來說,實際項目中,為了提高服務的響應能力,我們一般會通過負載均衡的方式,或者反向代理多個節點的方式來進行。通俗點來說,我們一般會將項目部署多實例,或者說部署多份,每個實例不同的啟動端口。但是每個實例的代碼其實都是一樣的。如果我們將定時任務寫在我們的項目中,就會面臨一個麻煩,就是比如我們部署了3個實例,三個實例一啟動,就會把定時任務都啟動,那么在同一個時間點,定時任務會一起執行,也就是會執行3次,這樣很可能會導致我們的業務出現錯誤。

一般來說,我們有幾種簡單的辦法來處理:

1、配置文件中增加自定義配置,通過開關來進行控制:比如增加:schedule=enable , schedule=disable,這樣在我們的實際代碼中,在進行判斷,也就是我們可以通過配置,達到,只有一個實例真正執行定時任務,其他的是實例不執行。但是,這種做法實際是還是定時任務都啟動,只是在執行中,我們人工來進行判斷,執行於不執行真正的處理邏輯。 
2、邏輯分離,就是我們將真正要定時任務處理的邏輯,寫成rest服務,或者rpc服務,然后我們可以新建一個單獨的定時任務項目,這個項目應該是沒有任何的業務代碼的,他純粹只有定時任務功能,幾點啟動,或者每隔多少時間啟動,啟動后,通過rest或者rpc的方式,調用真正處理邏輯的服務。同時,我們甚至可以不用新建一個項目,我們通過linux的cron就可以進行。同時,這種方式還有一個好處,比如有些時候,我們的定時任務也會因為某些原因出現問題,沒有執行,那么我們就可以通過curl 或者wget等等很多方式,再次定時任務的執行。

所以,個人一般偏向使用第二種方式,達到定時任務和業務處理的分離。

而在spring boot中,如何使用定時任務,相對比較簡單。按第二種方式,實際上,我需要新建一個項目來完成定時任務的功能,其實,我們完全可以新建一個普通的java項目,引入quartz來達到,但是這里,我是通過spring boot來完成,新建一個spring boot項目,項目的初始化可以使用:http://start.spring.io

初始化之后,我們在spring boot的入口類Application.java中,允許支持schedule

@SpringBootApplication @EnableScheduling public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 

然后,新建一個執行類Jobs.java

@Component public class Jobs { public final static long ONE_Minute = 60 * 1000; @Scheduled(fixedDelay=ONE_Minute) public void fixedDelayJob(){ System.out.println(Dates.format_yyyyMMddHHmmss(new Date())+" >>fixedDelay執行...."); } @Scheduled(fixedRate=ONE_Minute) public void fixedRateJob(){ System.out.println(Dates.format_yyyyMMddHHmmss(new Date())+" >>fixedRate執行...."); } @Scheduled(cron="0 15 3 * * ?") public void cronJob(){ System.out.println(Dates.format_yyyyMMddHHmmss(new Date())+" >>cron執行...."); } } 

這是最簡單的2種方式,多少分鍾執行一次,fixedDelay和fixedRate,單位是毫秒,所以1分鍾就是60秒×1000 他們的區別在於,fixedRate就是每多次分鍾一次,不論你業務執行花費了多少時間。我都是1分鍾執行1次,而fixedDelay是當任務執行完畢后1分鍾在執行。所以根據實際業務不同,我們會選擇不同的方式。

而還有一類定時任務,比如是每天的3點15分執行,那么我們就需要用另外一種方式:cron表達式

cron表達式,有專門的語法,而且感覺有點繞人,不過簡單來說,大家記住一些常用的用法即可,特殊的語法可以單獨去查。 
cron一共有7位,但是最后一位是年,可以留空,所以我們可以寫6位:

* 第一位,表示秒,取值0-59
* 第二位,表示分,取值0-59
* 第三位,表示小時,取值0-23
* 第四位,日期天/日,取值1-31
* 第五位,日期月份,取值1-12
* 第六位,星期,取值1-7,星期一,星期二...,注:不是第1周,第二周的意思
          另外:1表示星期天,2表示星期一。
* 第7為,年份,可以留空,取值1970-2099

cron中,還有一些特殊的符號,含義如下:

(*)星號:可以理解為每的意思,每秒,每分,每天,每月,每年...
(?)問號:問號只能出現在日期和星期這兩個位置,表示這個位置的值不確定,每天3點執行,所以第六位星期的位置,我們是不需要關注的,就是不確定的值。同時:日期和星期是兩個相互排斥的元素,通過問號來表明不指定值。比如,1月10日,比如是星期1,如果在星期的位置是另指定星期二,就前后沖突矛盾了。
(-)減號:表達一個范圍,如在小時字段中使用“10-12”,則表示從10到12點,即10,11,12
(,)逗號:表達一個列表值,如在星期字段中使用“1,2,4”,則表示星期一,星期二,星期四
(/)斜杠:如:x/y,x是開始值,y是步長,比如在第一位(秒) 0/15就是,從0秒開始,每15秒,最后就是0,15,30,45,60    另:*/y,等同於0/y

下面列舉幾個例子供大家來驗證:

0 0 3 * * ?     每天3點執行  
0 5 3 * * ?     每天3點5分執行  
0 5 3 ? * *     每天3點5分執行,與上面作用相同  
0 5/10 3 * * ?  每天3點的 5分,15分,25分,35分,45分,55分這幾個時間點執行  
0 10 3 ? * 1    每周星期天,3點10分 執行,注:1表示星期天  
0 10 3 ? * 1#3  每個月的第三個星期,星期天 執行,#號只能出現在星期的位置  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM