[spring]task標(biāo)簽及定時(shí)任務(wù)相關(guān)

一、 前言

本篇文章主要分為以下三部分:

  • applicationContext.xml-task命名空間中各配置項(xiàng)的回顧及梳理;
  • Spring Task定時(shí)任務(wù)的實(shí)現(xiàn)(注解方式);
  • Spring Task的問(wèn)題及相關(guān)解決方式;

本次學(xué)習(xí)使用的Spring的版本:4.3.15。

二、Spring Task

1. 半注解半XML配置

??前面我們依次重新梳理了beans,mvc,context等命名空間的各項(xiàng)配置,今天我們?cè)賮?lái)看一下task命名空間的一些配置項(xiàng)。簡(jiǎn)單來(lái)說(shuō),task命名空間是Spring Framework中用于支持定時(shí)任務(wù)和異步調(diào)用的。首先,要引入task命名空間:

xmlns:task="http://www.springframework.org/schema/task

http://www.springframework.org/schema/task 
http://www.springframework.org/schema/task/spring-task.xsd
1.1 task:annotation-driven標(biāo)簽

該標(biāo)簽是task命名空間中最基礎(chǔ)的標(biāo)簽,用于開(kāi)啟定時(shí)任務(wù)和異步調(diào)用的注解支持,來(lái)簡(jiǎn)單看下該注解的幾個(gè)屬性:

  1. scheduler,配置對(duì)應(yīng)的定時(shí)任務(wù)對(duì)象,如果沒(méi)有配置,默認(rèn)將使用TaskScheduler實(shí)例;
  2. executor,配置對(duì)應(yīng)的異步執(zhí)行的對(duì)象,如果沒(méi)有配置,默認(rèn)將使用SimpleAsyncTaskExecutor實(shí)例;
  3. exception-handler,在異步執(zhí)行期間,拋出的異常的實(shí)例,默認(rèn)使用SimpleAsyncUncaughtExceptionHandler,拋出的異常不會(huì)被程序捕獲到;
  4. proxy-target-class,是否要?jiǎng)?chuàng)建CGLIB代理,默認(rèn)是false,也就是創(chuàng)建的是基于Java接口的代理;
  5. mode,異步調(diào)用的模式,默認(rèn)的異步調(diào)用是通過(guò)Spring AOP來(lái)實(shí)現(xiàn)的,不過(guò)我們可以通過(guò)該屬性指定是否需要使用Aspectj的支持,使用Spring Aop代理時(shí)還可以通過(guò)proxy-target-class屬性指定是否需要強(qiáng)制使用CGLIB做基于Class的代理;該屬性有兩個(gè)選項(xiàng):proxyaspectj;
1.2 task:executor標(biāo)簽

該標(biāo)簽是用于對(duì)任務(wù)執(zhí)行的通用配置,可用于執(zhí)行不同的任務(wù)策略:同步的,異步的,使用線程池的等。通常用于異步調(diào)用,來(lái)簡(jiǎn)單看下屬性:

  1. id,這個(gè)不多說(shuō)了,對(duì)應(yīng)的executor的實(shí)例名稱(chēng),通常是executor,也就是ThreadPoolTaskExecutor實(shí)例,一般用于Async異步執(zhí)行的時(shí)候需要配置對(duì)應(yīng)的executor;
  2. pool-size,線程池中線程的數(shù)量(單個(gè)值或者范圍,如5-10);如果為10,表示核心線程數(shù)是10,最大線程數(shù)也是10;如果是5-10,表示核心線程數(shù)是5,最大線程數(shù)是10;如果不指定,則默認(rèn)核心線程數(shù)是1,最大線程數(shù)是Integer.MAX_VALUE;最大線程數(shù)只有在隊(duì)列容量不是無(wú)限制的時(shí)候才有用;
  3. queue-capacity,隊(duì)列容量,如果沒(méi)有指定,默認(rèn)Integer.MAX_VALUE;
  4. keep-alive,表示超過(guò)核心線程數(shù)的線程 在完成任務(wù)之后,處于空閑狀態(tài)的時(shí)間限制,也就是說(shuō)過(guò)了這段時(shí)間之后,線程會(huì)終止掉,單位是秒;為0會(huì)導(dǎo)致多余的線程在執(zhí)行完任務(wù)后立即終止,而不需要在任務(wù)隊(duì)列中執(zhí)行后續(xù)工作;
  1. rejection-policy,線程池中的任務(wù)隊(duì)列滿(mǎn)了以后對(duì)于新任務(wù)的處理策略:
    • ABORT 默認(rèn),拋出異常,然后不執(zhí)行相應(yīng)的任務(wù);
    • DISCARD 不執(zhí)行任務(wù),也不拋出異常,也就是忽略這個(gè)任務(wù);
    • DISCARD_OLDEST 將隊(duì)列中最舊的那個(gè)任務(wù)丟棄,執(zhí)行新任務(wù);
    • CALLER_RUNS 不在新線程中執(zhí)行任務(wù),而是強(qiáng)制由調(diào)用者所在的線程來(lái)執(zhí)行;
1.3 task:scheduler標(biāo)簽

該標(biāo)簽很簡(jiǎn)單,配置項(xiàng)不多,是用于定時(shí)任務(wù)相關(guān)的統(tǒng)一的配置,來(lái)看下屬性:

  1. id,用于定時(shí)調(diào)度任務(wù)的ThreadPoolTaskScheduler的bean的id,一般用于@Scheduled定時(shí)任務(wù)執(zhí)行;
  2. pool-size,定時(shí)調(diào)度線程池的大小,默認(rèn)是1;
1.4 task:scheduled-tasks和task:scheduled標(biāo)簽

??task:scheduled-tasks標(biāo)簽及其子標(biāo)簽task:scheduled是用于配置具體的定時(shí)任務(wù),該標(biāo)簽唯一的屬性scheduler指定定時(shí)任務(wù)使用的scheduler實(shí)例,我們來(lái)看下task:scheduled標(biāo)簽的一些屬性:

  1. ref,所引用的schedule的實(shí)例id;
  2. method,定時(shí)任務(wù)要調(diào)用的方法的名稱(chēng);
  3. cron,cron表達(dá)式,這個(gè)就不多介紹了,網(wǎng)上有許多詳細(xì)的介紹;
  4. fixed-delayfixed-rate,initial-delay,這三個(gè)屬性在前篇文章中已經(jīng)詳細(xì)介紹過(guò)了,這里不多介紹了,文章地址[spring注解]Spring相關(guān)注解(四),然后ctrl + f,搜索scheduled字符串即可;
  5. trigger,實(shí)現(xiàn)觸發(fā)器接口的bean;
1.5 簡(jiǎn)單總結(jié)

??到這,task標(biāo)簽的配置項(xiàng)已經(jīng)介紹完了,這里的配置其實(shí)是一種半注解半XML的形式,上述配置項(xiàng)配置完之后,我們就可以使用注解AsyncScheduled來(lái)完成我們的異步調(diào)用和定時(shí)調(diào)度任務(wù)了。簡(jiǎn)單貼一下我們常用的配置形式:

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
2. 注解形式
2.1 具體實(shí)現(xiàn)

??上面的配置其實(shí)不是完全注解的形式,在上述配置中,我們可以通過(guò)task:scheduled標(biāo)簽來(lái)執(zhí)行定時(shí)任務(wù),也可以通過(guò)注解@Scheduled來(lái)執(zhí)行定時(shí)任務(wù)。而這里,我們來(lái)看一下完全使用注解的形式:

  1. 在定時(shí)任務(wù)對(duì)象上添加兩個(gè)用于開(kāi)關(guān)的注解:@EnableScheduling(開(kāi)啟定時(shí)調(diào)度任務(wù)),@EnableAsync(開(kāi)啟異步執(zhí)行);
  2. 定義用于定時(shí)調(diào)用的schedule實(shí)例和用于異步執(zhí)行的executor實(shí)例,然后在需要調(diào)用的地方使用注解@Scheduled和@Async;

我們來(lái)簡(jiǎn)單看下實(shí)例:

@EnableWebMvc
@EnableScheduling
@EnableAsync
public class SpringConfig extends WebMvcConfigurerAdapter {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(5);
        return taskScheduler;
    }

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setMaxPoolSize(12);
        taskExecutor.setCorePoolSize(4);
        return taskExecutor;
    }
}

這里簡(jiǎn)單介紹下TaskExecutor

TaskExecutor,任務(wù)執(zhí)行接口,可用于執(zhí)行不同的任務(wù)策略:同步的,異步的,使用線程池的等;TaskExecutorjava.util.concurrent.Executor接口是相同的,實(shí)際上,它的存在主要是為了在使用線程池的時(shí)候,將對(duì)Java5的依賴(lài)抽象出來(lái);該接口只有一個(gè)execute(Runnable task)方法,它根據(jù)線程池的語(yǔ)義和配置,來(lái)接受一個(gè)執(zhí)行任務(wù)。

而針對(duì)執(zhí)行任務(wù)的@Scheduled和@Async注解,在前文也已經(jīng)介紹過(guò)了,這里就不再多介紹了,只簡(jiǎn)單說(shuō)下@Async:

Async 注解用于類(lèi)或者方法,用于標(biāo)識(shí)某個(gè)方法或某個(gè)類(lèi)的所有方法都是異步執(zhí)行的,方法被調(diào)用的時(shí)候,會(huì)在新線程中執(zhí)行,而調(diào)用它的方法會(huì)在原來(lái)的線程中執(zhí)行;

2.2 其他說(shuō)明
  1. TaskExecutor的實(shí)現(xiàn)類(lèi)有許多,比如SimpleAsyncTaskExecutorSyncTaskExecutor,ConcurrentTaskExecutorSimpleThreadPoolTaskExecutor,ThreadPoolTaskExecutor等,而這里最常用的就是基于線程池的實(shí)現(xiàn)ThreadPoolTaskExecutor,有興趣的可以看下它的常用參數(shù),都很簡(jiǎn)單這里就不多說(shuō)了,如果要了解更多,可以參考后面給出的官方文檔地址。
  1. @Async注解標(biāo)識(shí)的方法默認(rèn)會(huì)被<task:annotation-driven/>指定的TaskExecutor執(zhí)行,如果需要針對(duì)某個(gè)特定的異步執(zhí)行使用一個(gè)特定的TaskExecutor,則可以通過(guò)@Async的value屬性指定需要使用的TaskExecutor對(duì)應(yīng)的bean名稱(chēng);至于該注解的其他屬性,這里就不多說(shuō)了。
2.3 注意事項(xiàng)

??使用@Async注解標(biāo)識(shí)的方法進(jìn)行異步調(diào)用是通過(guò)Spring AOP來(lái)實(shí)現(xiàn)的,所以@Async注解的方法必須是public方法,且必須是外部調(diào)用。這點(diǎn)其實(shí)和Transactional注解是類(lèi)似的,也就是在同一個(gè)類(lèi)中,一個(gè)方法調(diào)用另一個(gè)有注解的方法,注解是不會(huì)生效的。

3. Spring Task的集群?jiǎn)栴}

??使用Spring的注解@Scheduled可以很方便的執(zhí)行一個(gè)定時(shí)任務(wù),單臺(tái)服務(wù)器下是沒(méi)有問(wèn)題的,如果在多臺(tái)服務(wù)器做負(fù)載均衡的情況下,有可能會(huì)出現(xiàn)定時(shí)任務(wù)的重復(fù)執(zhí)行的問(wèn)題,因?yàn)榧旱母髋_(tái)服務(wù)器之間數(shù)據(jù)是不會(huì)共享的,每臺(tái)服務(wù)器上的任務(wù)都會(huì)按時(shí)執(zhí)行。這種情況其實(shí)就是集群環(huán)境下的狀態(tài)同步問(wèn)題,針對(duì)這種情況,我們需要考慮的就是如何保證在同一時(shí)刻只有一個(gè)任務(wù)在執(zhí)行。

3.1 借助數(shù)據(jù)庫(kù)Mysql來(lái)實(shí)現(xiàn)

??由于數(shù)據(jù)庫(kù)本身的鎖機(jī)制,我們可以借助數(shù)據(jù)庫(kù)來(lái)實(shí)現(xiàn),其實(shí)也就是說(shuō)兩個(gè)任務(wù)同時(shí)操作表里的一條記錄,只有一條能操作成功,我們可以借助數(shù)據(jù)庫(kù)排他鎖的這個(gè)特性來(lái)實(shí)現(xiàn)。

當(dāng)然這種情況有一些問(wèn)題需要考慮,比如說(shuō)某臺(tái)服務(wù)器掛了數(shù)據(jù)庫(kù)鎖的釋放,表中記錄狀態(tài)的維護(hù)等。

3.2 基于redis實(shí)現(xiàn)

??其實(shí)這個(gè)問(wèn)題就是一個(gè)任務(wù)互斥的問(wèn)題,如果學(xué)過(guò)操作系統(tǒng)的話(huà),就很好理解,就是操作系統(tǒng)中所謂的PV操作,也就是說(shuō),聲明一個(gè)分布式互斥鎖,一臺(tái)服務(wù)器獲得鎖后,改變鎖狀態(tài),然后執(zhí)行任務(wù),任務(wù)完成還原狀態(tài);另一臺(tái)服務(wù)器獲取鎖,如果是鎖定狀態(tài),說(shuō)明正在有其他服務(wù)器執(zhí)行,不執(zhí)行任何操作,直接結(jié)束。

  1. 我們可以借助Redis的原子特性,使用遞增遞減方法來(lái)實(shí)現(xiàn),如果使用jedis來(lái)操作的話(huà),使用incr和decr來(lái)實(shí)現(xiàn);
  2. 無(wú)論任務(wù)是否執(zhí)行成功,一定要記得執(zhí)行狀態(tài)的還原,也就是鎖失效狀態(tài),這種可以通過(guò)指定超時(shí)時(shí)間來(lái)實(shí)現(xiàn);同樣,如果服務(wù)器重啟,記得重置該鎖的狀態(tài);
  3. 超時(shí)時(shí)間的設(shè)置不要大于兩次任務(wù)間隔的時(shí)間;

簡(jiǎn)單寫(xiě)下代碼:

String cacheKeyPV = ...;

@PostConstruct
public void init() {
    // 封裝方法,設(shè)置超時(shí)時(shí)間等
    jedis.incr(cacheKeyPV);
}

try {
    //P:設(shè)置為1
    // 借助jedis的expire等方法簡(jiǎn)單封裝一下incr
    long resources = jedis.incr(cacheKeyPV,10, CacheTimeUnit.SECOND);
    if(resources == 1) {
        // 執(zhí)行操作
    }
} catch (Exception e) {
    // exception
} finally {
    try {
        //V:重新設(shè)置為0
        jedis.decr(cacheKeyPV) ;
    } catch (Exception e) {
        // catch
    }
}
3.3 Quartz框架

??Quartz框架是一個(gè)優(yōu)秀的框架,功能十分強(qiáng)大,支持集群環(huán)境下的任務(wù)調(diào)度,但功能有點(diǎn)復(fù)雜,對(duì)于一些常規(guī)的簡(jiǎn)單的項(xiàng)目,是不建議使用的,如果有興趣的,可以了解下。

3.4 借助一些開(kāi)源的分布式任務(wù)調(diào)度系統(tǒng)

??目前,市面上有許多開(kāi)源的分布式的任務(wù)調(diào)度系統(tǒng),比如說(shuō)LTS,Elastic-Job,Uncode-Schedule等,如果需要了解更多,可以參考:https://my.oschina.net/editorial-story/blog/883856

參考地址:
官方文檔:Spring4.3.15.Release-docs-html-scheduling
官方文檔的中文翻譯可參考簡(jiǎn)書(shū)的一篇文章:http://www.itdecent.cn/p/69e44b93bb47
Spring API地址:https://docs.spring.io/spring/docs/
其他參考自:在同一個(gè)類(lèi)中,一個(gè)方法調(diào)用另外一個(gè)有注解方法失敗的原因
另外,剛興趣的童鞋可以再去看下操作系統(tǒng)的PV操作,這個(gè)很有意思的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,030評(píng)論 25 709
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,272評(píng)論 6 342
  • 生活啊,多的是呻吟,更多的人感覺(jué)生活就像一灘泥澤,被昏暗的天空,渾濁的空氣壓抑著。 在我曾經(jīng)的角度去看待這個(gè)世界是...
    成為自己蠻閱讀 776評(píng)論 1 2
  • 身體力行和感覺(jué)認(rèn)知。 1,文字,單就以本身的存在形式而言,更偏向于感覺(jué)認(rèn)知,除非你練習(xí)書(shū)法,繪畫(huà),你有筆墨紙硯,這...
    魯莽書(shū)生閱讀 771評(píng)論 0 0

友情鏈接更多精彩內(nèi)容