Spring中使用Quartz來做單機任務(wù)調(diào)度

背景

開發(fā)Java應(yīng)用經(jīng)常會需要用到單機定時任務(wù)暑认,這個時候一般我們會采用分布式任務(wù)調(diào)度中間件來解決問題。典型的分布式任務(wù)調(diào)度中間件大审,比如淘寶的tbschedule蘸际,當(dāng)當(dāng)?shù)膃lastic-job,唯品會的saturn徒扶。京東沒做開源粮彤,大概率是用了tbschedule。

但是姜骡,分布式任務(wù)調(diào)度中間件往往是基于一個zookeeper集群來做任務(wù)調(diào)度的导坟。?如果zookeeper集群出了問題,任務(wù)調(diào)度就掛街了圈澈。

這個時候其實可以直接基于quartz來做調(diào)度惫周,這也是本文要說的事情。

QuartzTask

首先定義一個任務(wù)接口康栈,我們希望一個任務(wù)實現(xiàn)下面這些方法:

public interface QuartzTask {

    /**
     * 任務(wù)執(zhí)行的代碼
     */
    void executeTask();

    /**
     * 指定執(zhí)行任務(wù)的機器ip
     *
     * @return String
     */
    String getTargetIP();

    /**
     * crond表達式
     *
     * @return String
     */
    String getCrond();

    /**
     * 任務(wù)唯一名稱
     *
     * @return String
     */
    String getJobName();
}

EmptyTask

為了解釋清楚如何使用quartz來做單機任務(wù)調(diào)度递递,我們新建一個空白的任務(wù)EmptyTask并實現(xiàn)QuartzTask定義的方法:

@Service("emptyTask")
public class EmptyTask implements QuartzTask {

    private static final Logger logger = LoggerFactory.getLogger(EmptyTask.class);

    public void executeTask() {
        logger.warn("emptyTask running");
    }

    @Override
    public String getTargetIP() {
        return "127.0.0.1";
    }

    @Override
    public String getCrond() {
        return "0 0/1 * * * ?";
    }

    @Override
    public String getJobName() {
        return "emptyTask";
    }
}

targetIP即可以寫死成一個字符串,也可以從配置服務(wù)谅将、第三方接口漾狼,數(shù)據(jù)庫等數(shù)據(jù)源動態(tài)的獲取。

crond表達式可以在 http://cron.qqe2.com/ 動態(tài)生成饥臂。

BeanPostProcessor

BeanPostProcessor是Spring中一個很實用的接口逊躁。BeanPostProcessor提供了兩個方法:

public interface BeanPostProcessor {

    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

postProcessBeforeInitialization:Bean 實例化之前進行的處理
postProcessAfterInitialization: Bean 實例化之后進行的處理

使用BeanPostProcessor也很簡單,只要實現(xiàn)這個接口隅熙,并重載這兩個方法就可以了稽煤。

SchedulerManager

利用BeanPostProcessor提供的特性,我們可以在所有bean初始化完成之后做一些事情囚戚。于是就有了SchedulerManager類:

public class SchedulerManager extends SchedulerFactoryBean implements BeanPostProcessor, InitializingBean {

    private Scheduler scheduler;

    public void afterPropertiesSet() throws Exception {
        super.afterPropertiesSet();
        this.scheduler = this.getScheduler();
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        try {
            if (QuartzTask.class.isInstance(bean)) {
                QuartzTask quartzTask = QuartzTask.class.cast(bean);

                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzTask.getCrond());
                Trigger newTrigger = TriggerBuilder.newTrigger()
                        .withIdentity(quartzTask.getJobName(), "DEFAULT").withSchedule(scheduleBuilder).build();

                //需要一個中間job類來處理酵熙,中間類會去實際執(zhí)行任務(wù)
                JobDetail jobDetail = JobBuilder.newJob(ScheduleTaskExecutor.class)
                        .withIdentity(quartzTask.getJobName(), "DEFAULT").build();
                jobDetail.getJobDataMap().put("taskContext", quartzTask);

                scheduler.scheduleJob(jobDetail, newTrigger);
            }
        } catch (Exception e) {
            logger.error("scheduleManager init error.", e);
        }
        return bean;
    }
}

ScheduleTaskExecutor

由于JobDetail自己管理了任務(wù)執(zhí)行類的生命周期,所以只能使用一個中間任務(wù)類ScheduleTaskExecutor驰坊,中轉(zhuǎn)到要實際執(zhí)行的任務(wù)匾二。

DisallowConcurrentExecution注解能使得ScheduleTaskExecutor的同一個實例,不會并發(fā)執(zhí)行execute方法。也就是說同一個JobDetail不會并發(fā)執(zhí)行察藐。但是如果是不同的JobDetail皮璧,是可以并發(fā)執(zhí)行的。

@DisallowConcurrentExecution
public class ScheduleTaskExecutor implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        QuartzTask quartzTask = (QuartzTask)context.getMergedJobDataMap().get("taskContext");

        if (!HttpUtil.getServerIp().equals(quartzTask.getTargetIP())) {
            return;
        }
        quartzTask.executeTask();
    }
}

存在的問題

上面的實現(xiàn)方式存在一個待優(yōu)化的問題:線上應(yīng)用往往是多機器分布式的分飞,雖然指定了某個IP執(zhí)行任務(wù)悴务,但是其實每臺機器都開啟了一個定時任務(wù)去執(zhí)行中間任務(wù)類的的方法,只不過在IP判斷的時候提前結(jié)束了任務(wù)譬猫。

更好的實現(xiàn)方式讯檐,是監(jiān)控targetIP的變化,然后暫停or開啟任務(wù)染服。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咽弦,一起剝皮案震驚了整個濱河市携冤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖肾请,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漓踢,死亡現(xiàn)場離奇詭異赖草,居然都是意外死亡滴某,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門站宗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闸准,“玉大人,你說我怎么就攤上這事梢灭∫募遥” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵敏释,是天一觀的道長库快。 經(jīng)常有香客問我,道長钥顽,這世上最難降的妖魔是什么义屏? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮蜂大,結(jié)果婚禮上闽铐,老公的妹妹穿的比我還像新娘。我一直安慰自己奶浦,他們只是感情好兄墅,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著澳叉,像睡著了一般隙咸。 火紅的嫁衣襯著肌膚如雪沐悦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天扎瓶,我揣著相機與錄音所踊,去河邊找鬼。 笑死概荷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碌燕。 我是一名探鬼主播误证,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼修壕!你這毒婦竟也來了愈捅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤慈鸠,失蹤者是張志新(化名)和其女友劉穎蓝谨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體青团,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡譬巫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了督笆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芦昔。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖娃肿,靈堂內(nèi)的尸體忽然破棺而出咕缎,到底是詐尸還是另有隱情,我是刑警寧澤料扰,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布凭豪,位于F島的核電站,受9級特大地震影響晒杈,放射性物質(zhì)發(fā)生泄漏嫂伞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一桐智、第九天 我趴在偏房一處隱蔽的房頂上張望末早。 院中可真熱鬧,春花似錦说庭、人聲如沸然磷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姿搜。三九已至寡润,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舅柜,已是汗流浹背梭纹。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留致份,地道東北人变抽。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像氮块,于是被迫代替她去往敵國和親绍载。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容