Spring排程定時任務的方式

weixin_34321977發表於2019-01-13

spring排程定時任務的方式

spring 定時器任務scheduled-tasks預設配置是單執行緒序列執行的,多個任務相當於序列。每個job都是等待上個執行完了才執行下一個job。這就造成了若某個任務執行時間過長,其他任務一直在排隊,業務邏輯沒有及時處理的問題。

單執行緒執行定時任務帶來的問題

spring排程定時任務的方式就會導致:bTask會因為aTask的超時執行而延遲執行。

如下是scheduled定義了3個任務。

<task:scheduled-tasks >
  <task:scheduled ref="myTask1" method="run" cron="0 0/59 10-23 * * ?"/>
  <task:scheduled ref="myTask2" method="run" cron="0/10 * * * * ?"/>
  <task:scheduled ref="myTask3" method="run" cron="0/10 * * * * ?"/>
</task:scheduled-tasks>

檢視該任務17點的執行日誌(task名字已修改)

zgrep -e '2016-10-28 17:' channel-task.log.2016-10-28.log.gz | grep -e 'MyTask2' 

2016-10-28 17:14:25,002 INFO [pool-8-thread-1 - ] task.AbstractTask - [TASK] start task >> .MyTask2@186d315
2016-10-28 17:14:35,980 INFO [pool-8-thread-1 - ] task.AbstractTask - [TASK] complete task MyTask2@186d315
2016-10-28 17:14:40,002 INFO [pool-8-thread-1 - ] task.AbstractTask - [TASK] start task >> .MyTask2@186d315
2016-10-28 17:14:50,681 INFO [pool-8-thread-1 - ] task.AbstractTask - [TASK] complete task .MyTask2@186d315
2016-10-28 17:14:55,003 INFO [pool-8-thread-1 - ] task.AbstractTask - [TASK] start task >> .MyTask2@186d315
2016-10-28 17:15:05,613 INFO [pool-8-thread-1 - ] task.AbstractTask - [TASK] complete task MyTask2@186d315

2016-10-28 17:20:35,246 INFO [pool-8-thread-1 - ] task.AbstractTask - [TASK] start task >> .MyTask2@186d315
2016-10-28 17:20:46,051 INFO [pool-8-thread-1 - ] task.AbstractTask - [TASK] complete task .MyTask2@186d315

2016-10-28 17:20:50,003 INFO [pool-8-thread-1 - ] task.AbstractTask - [TASK] start task >> .MyTask2@186d315
2016-10-28 17:21:00,974 INFO [pool-8-thread-1 - ] task.AbstractTask - [TASK] complete task MyTask2@186d315

MyTask2每10秒鐘執行一次,但是在17:15:05 到17:20:35之間,5分鐘內定時任務沒有執行。

執行命令 zgrep -e '2016-10-28 17:1' task.log.2016-10-28.log.gz (當天17點10幾分發生的日誌)。然後在查詢日誌發現,這5分鐘之內有大量的日誌在執行。

2016-10-28 17:17:20,202 INFO [pool-8-thread-1 - ] task.MyTask3 - compare query order[ 211621610280893418 ] 
2016-10-28 17:17:20,477 INFO [pool-8-thread-1 - ] task.MyTask3 - compare query order[ 211621610280893401 ] 
2016-10-28 17:17:20,731 INFO [pool-8-thread-1 - ] task.MyTask3 - compare query order[ 211621610280893402 ] 
.........中間省略n條日誌

2016-10-28 17:19:59,752 INFO [pool-8-thread-1 - ] task.MyTask3 - compare query order[ 211621610280894049 ]

通過過以上日誌可以看出,該執行緒[pool-8-thread-1 - ] 一直在處理MyTask3任務,此時斷定 task:scheduled 配置預設是單執行緒序列的。網上查詢資料發現如下配置可以解決問題:

<task:scheduler id="scheduler" pool-size="3" />
<task:scheduled-tasks scheduler="scheduler" >
  <task:scheduled ref="myTask1" method="run" cron="0 0/59 11-23 * * ?"/>
  <task:scheduled ref="myTask2" method="run" cron="0/10 * * * * ?"/>
  <task:scheduled ref="myTask3" method="run" cron="0/10 * * * * ?"/>
</task:scheduled-tasks>

MethodInvokingJobDetailFactoryBean併發問題

Spring中可以通過配置方便的實現週期性定時任務管理,這需要用到以下幾個類:

  • MethodInvokingJobDetailFactoryBean:此工廠主要用來製作一個jobDetail,即製作一個任務。由於我們所做的定時任務根本上講其實就是執行一個方法。所以用這個工廠比較方便。
  • SimpleTriggerBean:定時器,負責配置啟動時間、執行週期。
  • SchedulerFactoryBean:觸發器,負責配置所有定時器。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">

    <bean id="myJob1Obj" class="com.vip.service.job.MyJob1" />

    <bean id="myJob1"
        class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="myJob1Obj" />
        <property name="targetMethod" value="doJob" />
        <property name="concurrent" value="false" />
    </bean>

    <bean id="myJob1CronTrigger"
        class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="myJob1" />
        <property name="cronExpression" value="0/3 * * * * ?"></property>
    </bean>

    <bean id="myJob2Obj" class="com.vip.service.job.MyJob2" />

    <bean id="myJob2" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="myJob2Obj" />
        <property name="targetMethod" value="doJob" />
        <property name="concurrent" value="false" />
    </bean>

    <bean id="myJob2CronTrigger"
          class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="myJob2" />
        <property name="cronExpression" value="0/5 * * * * ?"></property>
    </bean>

    <bean id="myJob3Obj" class="com.vip.service.job.MyJob3" />

    <bean id="myJob3"
        class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="myJob3Obj" />
        <property name="targetMethod" value="doJob" />
        <property name="concurrent" value="false" />
    </bean>

    <bean id="myJob3CronTrigger"
        class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="myJob3" />
        <property name="cronExpression" value="0/5 * * * * ?"></property>
    </bean>

    <bean id="jobQuertz"
        class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
        lazy-init="false">
        <property name="applicationContextSchedulerContextKey" value="applicationContext" />
        <property name="jobDetails">
            <list>
                <ref bean="myJob3" />
                <ref bean="myJob1" />
                <ref bean="myJob2" />
            </list>
        </property>
        <property name="schedulerName" value="AdsScheduler" />
        <property name="triggers">
            <list>
                <ref bean="myJob3CronTrigger" />
                <ref bean="myJob2CronTrigger" />
                <ref bean="myJob1CronTrigger" />
            </list>
        </property>
        <property name="startupDelay" value="15" />
        <property name="overwriteExistingJobs" value="true" />
        <property name="autoStartup" value="true" />
    </bean>

</beans>

concurrent:對於相同的JobDetail,當指定多個Trigger時, 很可能第一個job完成之前,第二個job就開始了。定concurrent設為false,多個job不會併發執行,第二個job將不會在第一個job完成之前開始。

MethodInvokingJobDetailFactoryBean類預設是併發執行的,這時候如果不設定“concurrent”為false,很可能帶來併發或者死鎖的問題,而且機率較小,不容易復現,請大家使用的時候注意設定“concurrent”。

相關文章