SpringBoot整合任務(wù)調(diào)度框架Quartz的基礎(chǔ)搭建

Quartz的整體概括

什么是quartz

何為quartz,請(qǐng)看官網(wǎng)的說(shuō)法:

Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone application to the largest e-commerce system. Quartz can be used to create simple or complex schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks are defined as standard Java components that may execute virtually anything you may program them to do. The Quartz Scheduler includes many enterprise-class features, such as support for JTA transactions and clustering.

簡(jiǎn)單來(lái)說(shuō),quartz是一個(gè)開源任務(wù)調(diào)度庫(kù),可以用來(lái)創(chuàng)建簡(jiǎn)單或復(fù)雜的調(diào)度,低至十個(gè)多至數(shù)百萬(wàn)個(gè)。它是一個(gè)標(biāo)準(zhǔn)的java組件,支持JTA,集群等多種企業(yè)級(jí)功能。

市面上有很多定時(shí)任務(wù)框架在quartz的基礎(chǔ)上做了二次開發(fā),xxl-job(基于quartz),elastic-job(基于quartz和zk),所以quartz到底是怎么玩的,它有哪些特性,下面來(lái)聊一聊。

quartz的基本概念

  • 任務(wù)(Job):實(shí)際要觸發(fā)的事件
  • 觸發(fā)器(Trigger):用于設(shè)定時(shí)間規(guī)則
  • 調(diào)度器(Scheduler):組合任務(wù)與觸發(fā)器

quartz就這三樣?xùn)|西,我們新建作業(yè),通過(guò)trigger設(shè)置規(guī)則觸發(fā),由scheduler進(jìn)行整合,非常簡(jiǎn)單。

Springboot整合quartz的基礎(chǔ)搭建

一般企業(yè)級(jí)項(xiàng)目開發(fā)都用的Springboot,下面就來(lái)講一講quartz整合Springboot的一些要點(diǎn)。

依賴

quartz版本2.3.0,springboot版本1.5.18.RELEASE

<properties>
        <java.version>1.8</java.version>
        <druid.version>1.1.5</druid.version>
        <quartz.version>2.3.0</quartz.version>
        <fastjson.version>1.2.40</fastjson.version>
        <mybatis.version>1.3.0</mybatis.version>
        <log4j.version>1.2.16</log4j.version>
        <slf4j-api.version>1.7.7</slf4j-api.version>
        <slf4j-log4j12.version>1.7.7</slf4j-log4j12.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!--quartz相關(guān)依賴-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>${quartz.version}</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>${quartz.version}</version>
        </dependency>
        <!--定時(shí)任務(wù)需要依賴context模塊-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!-- log4j日志 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j-api.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j-log4j12.version}</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

Configuration

通過(guò)AutowireCapableBeanFactory,使用spring注入的方式實(shí)現(xiàn)在job里注入springbean。

/**
     * 繼承org.springframework.scheduling.quartz.SpringBeanJobFactory
     * 實(shí)現(xiàn)任務(wù)實(shí)例化方式
     */
    public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
            ApplicationContextAware {

        private transient AutowireCapableBeanFactory beanFactory;

        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }

        /**
         * 將job實(shí)例交給spring ioc托管
         * 我們?cè)趈ob實(shí)例實(shí)現(xiàn)類內(nèi)可以直接使用spring注入的調(diào)用被spring ioc管理的實(shí)例
         *
         * @param bundle
         * @return
         * @throws Exception
         */
        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
            final Object job = super.createJobInstance(bundle);
            /**
             * 將job實(shí)例交付給spring ioc
             */
            beanFactory.autowireBean(job);
            return job;
        }
    }

    /**
     * 配置任務(wù)工廠實(shí)例
     *
     * @param applicationContext spring上下文實(shí)例
     * @return
     */
    @Bean
    public JobFactory jobFactory(ApplicationContext applicationContext) {
        /**
         * 采用自定義任務(wù)工廠 整合spring實(shí)例來(lái)完成構(gòu)建任務(wù)
         * see {@link AutowiringSpringBeanJobFactory}
         */
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }

    /**
     * 配置任務(wù)調(diào)度器
     * 使用項(xiàng)目數(shù)據(jù)源作為quartz數(shù)據(jù)源
     *
     * @param jobFactory 自定義配置任務(wù)工廠
     * @param dataSource 數(shù)據(jù)源實(shí)例
     * @return
     * @throws Exception
     */
    @Bean(destroyMethod = "destroy", autowire = Autowire.NO)
    public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        //將spring管理job自定義工廠交由調(diào)度器維護(hù)
        schedulerFactoryBean.setJobFactory(jobFactory);
        //設(shè)置覆蓋已存在的任務(wù)
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        //項(xiàng)目啟動(dòng)完成后,等待2秒后開始執(zhí)行調(diào)度器初始化
        schedulerFactoryBean.setStartupDelay(2);
        //設(shè)置調(diào)度器自動(dòng)運(yùn)行
        schedulerFactoryBean.setAutoStartup(true);
        //設(shè)置數(shù)據(jù)源,使用與項(xiàng)目統(tǒng)一數(shù)據(jù)源
        schedulerFactoryBean.setDataSource(dataSource);
        //設(shè)置上下文spring bean name
        schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
        //設(shè)置配置文件位置
        schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
        return schedulerFactoryBean;
    }

這里需要提到一點(diǎn),由于job的初始化時(shí)是通過(guò)new出來(lái)的,不受spring的管理,無(wú)法接受業(yè)務(wù)相關(guān)的bean,故這里使用AutowireCapableBeanFactory實(shí)現(xiàn)了new出來(lái)的對(duì)象通過(guò)注解可注入受spring管理的bean了。

AbstractAutowireCapableBeanFactory#autowireBean

@Override
    public void autowireBean(Object existingBean) {
        // Use non-singleton bean definition, to avoid registering bean as dependent bean.
        RootBeanDefinition bd = new RootBeanDefinition(ClassUtils.getUserClass(existingBean));
        bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        bd.allowCaching = ClassUtils.isCacheSafe(bd.getBeanClass(), getBeanClassLoader());
        BeanWrapper bw = new BeanWrapperImpl(existingBean);
        initBeanWrapper(bw);
        populateBean(bd.getBeanClass().getName(), bd, bw);
    }

由源碼可知,此類調(diào)用了populateBean的方法用來(lái)裝配bean。具體spring的bean的加載注冊(cè)過(guò)程可參考spring.io。

通過(guò)schedulerFactoryBeanConfigLocation來(lái)讀取quartz的基本配置信息,注意quartz.properties配置文件一定要放在classpath下。

#調(diào)度器實(shí)例名稱
org.quartz.scheduler.instanceName = quartzScheduler

#調(diào)度器實(shí)例編號(hào)自動(dòng)生成
org.quartz.scheduler.instanceId = AUTO

#持久化方式配置
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

#持久化方式配置數(shù)據(jù)驅(qū)動(dòng),MySQL數(shù)據(jù)庫(kù)
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#quartz相關(guān)數(shù)據(jù)表前綴名
org.quartz.jobStore.tablePrefix = QRTZ_

#開啟分布式部署
org.quartz.jobStore.isClustered = true
#配置是否使用
org.quartz.jobStore.useProperties = false

#分布式節(jié)點(diǎn)有效性檢查時(shí)間間隔,單位:毫秒
org.quartz.jobStore.clusterCheckinInterval = 10000

#線程池實(shí)現(xiàn)類
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

#執(zhí)行最大并發(fā)線程數(shù)量
org.quartz.threadPool.threadCount = 10

#線程優(yōu)先級(jí)
org.quartz.threadPool.threadPriority = 5

#配置為守護(hù)線程,設(shè)置后任務(wù)將不會(huì)執(zhí)行
#org.quartz.threadPool.makeThreadsDaemons=true

#配置是否啟動(dòng)自動(dòng)加載數(shù)據(jù)庫(kù)內(nèi)的定時(shí)任務(wù),默認(rèn)true
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

我們看到org.quartz.jobStore.class進(jìn)行持久化配置設(shè)置成了JobStoreTX屬性,需要建立數(shù)據(jù)庫(kù)表進(jìn)行任務(wù)信息的持久化。其實(shí)官方還有一種RAMJobStore用于存儲(chǔ)內(nèi)存中的調(diào)度信息,當(dāng)進(jìn)程終止時(shí),所有調(diào)度信息都將丟失。本文使用JobStoreTX(需要建立quartz的大概10張表,建表語(yǔ)句傳在了github)。

Job&JobDetail

JobDetail作為Job的實(shí)例,一般由靜態(tài)方法JobBuilder創(chuàng)建,通過(guò)fluent風(fēng)格鏈?zhǔn)綐?gòu)建了Job的各項(xiàng)屬性,

其中newJob需要一個(gè)泛型上限為Job的入?yún)ⅰ?/p>

// 構(gòu)建job信息
JobDetail job = JobBuilder.newJob(DynamicQuartzJob.class)
          .withIdentity(jobKey) //jobName+jobGroup
          .withDescription(quartzJobDetails.getDescription())
          .usingJobData("jobData", quartzJobDetails.getJobData())
          .build();

而Job接口只有一個(gè)簡(jiǎn)單的方法:

public interface Job {
    void execute(JobExecutionContext context)
        throws JobExecutionException;
}

當(dāng)定時(shí)任務(wù)跑起來(lái)的時(shí)候,execute里的代碼將會(huì)被執(zhí)行。

比如我們創(chuàng)建一個(gè)簡(jiǎn)單的定時(shí)任務(wù):

public class QuartzTest extends QuartzJobBean
static Logger logger = LoggerFactory.getLogger(QuartzTest.class);
{
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        logger.info("我是測(cè)試任務(wù),我跑起來(lái)了,時(shí)間:{}",new Date());
    }

注:QuartzJobBean是Job接口的實(shí)現(xiàn)類。

Trigger

JobDetailJobBuilder類的靜態(tài)方法構(gòu)建,同樣,Trigger觸發(fā)器由TriggerBuilder的靜態(tài)方法構(gòu)建。

// 構(gòu)建job的觸發(fā)規(guī)則 cronExpression
    Trigger trigger = TriggerBuilder.newTrigger()
            .withIdentity(triggerKey)
            .startNow()
            .withSchedule(CronScheduleBuilder
            .cronSchedule(quartzJobDetails.getCronExpression()))
            .build();

Trigger觸發(fā)器用于觸發(fā)任務(wù)作業(yè),當(dāng)trigger觸發(fā)器觸發(fā)執(zhí)行時(shí),scheduler調(diào)度程序中的其中一個(gè)線程將調(diào)用execute()的一個(gè)線程。quartz最常用的觸發(fā)器分為SimpleTriggerCronTrigger觸發(fā)器兩種。
SimpleTrigger用于在給定時(shí)間執(zhí)行一次作業(yè),或給定時(shí)間每隔一段時(shí)間執(zhí)行一次作業(yè)。這個(gè)功能Springboot@scheduled注解也能實(shí)現(xiàn)。
如果是希望以日歷時(shí)間表觸發(fā),則CronTrigger就比較合適例如每周六下午3點(diǎn)執(zhí)行,我們完全可以用cron表達(dá)式實(shí)現(xiàn)日歷觸發(fā)的時(shí)間規(guī)則,cron表達(dá)式可由quartzJobDetails對(duì)象的CronExpression屬性傳入。

最后,別忘了用schedulerjobtrigger整合起來(lái),因?yàn)樗麄兪墙y(tǒng)一協(xié)作的:

// 注冊(cè)job和trigger信息
scheduler.scheduleJob(job, trigger);

JobDataMap

一般業(yè)務(wù)方法會(huì)要求動(dòng)態(tài)傳參處理,這時(shí)候就需要jobDataMap來(lái)進(jìn)行參數(shù)傳遞了。我們?cè)跇?gòu)建JobDetail的時(shí)候,通過(guò)

usingJobData("jobData", quartzJobDetails.getJobData())

動(dòng)態(tài)傳入調(diào)度任務(wù)所需的參數(shù),以達(dá)到業(yè)務(wù)需求。

JobListener&TriggerListener

用于在任務(wù)調(diào)度期間,各階段的狀態(tài)解讀。這里我就以JobListener為例,TriggerListener也是相似的。

首先,構(gòu)建jobListener

jobContext.getScheduler().getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(jobKey));

這里我是在executeInternal方法里面構(gòu)建的,因?yàn)閘istner不會(huì)持久化,服務(wù)重啟將會(huì)丟失監(jiān)聽。當(dāng)然在構(gòu)建job的時(shí)候也可以注冊(cè)listener,如果沒(méi)持久化監(jiān)聽的需求的話。

看一下MyJobListener:

public class MyJobListener implements JobListener {
    public static final String LISTENER_NAME = "MyJobListener";

//    @Autowired
//    private JobScheduleLogMapper logMapper;

    @Override
    public String getName() {
        return LISTENER_NAME; //must return a name
    }

    //任務(wù)被調(diào)度前
    @Override
    public void jobToBeExecuted(JobExecutionContext context) {

        String jobName = context.getJobDetail().getKey().toString();
//        System.out.println("jobToBeExecuted");
        System.out.println("Job調(diào)度前 : " + jobName + " is going to start...");

    }

    //任務(wù)調(diào)度被拒了
    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        System.out.println("Job調(diào)度被拒:jobExecutionVetoed");
        //todo:原因捕獲

    }

    //任務(wù)被調(diào)度后
    @Override
    public void jobWasExecuted(JobExecutionContext context,
                               JobExecutionException jobException) {
//        System.out.println("Job調(diào)度器:jobWasExecuted");

        String jobName = context.getJobDetail().getKey().toString();
        System.out.println("Job調(diào)度后 : " + jobName + " is finished...");

        if (jobException!=null&&!jobException.getMessage().equals("")) {
            System.out.println("Exception thrown by: " + jobName
                    + " Exception: " + jobException.getMessage());
        }
        JobScheduleLog log = new JobScheduleLog();
        log.setJobRuntime(String.valueOf(context.getJobRunTime()));
        log.setId(Optional.ofNullable(context.get("id")).map(p->Integer.parseInt(String.valueOf(context.get("id")))).orElse(null));
        JobScheduleLogMapper logMapper = SpringContextHolder.getBean(JobScheduleLogMapper.class);
        logMapper.updateByPrimaryKeySelective(log);
    }
}

任務(wù)調(diào)度前,調(diào)度后已經(jīng)任務(wù)被拒,我們都可以使用鉤子。

動(dòng)態(tài)構(gòu)建任務(wù)調(diào)度

下一個(gè)問(wèn)題,我們知道新建一個(gè)調(diào)度job只要繼承QuartzJobBean類并實(shí)現(xiàn)executeInternal就行,那么如果我有成百上千個(gè)任務(wù),難道我要新建幾千個(gè)類么?如果我想把已有的方法加入定時(shí)任務(wù)調(diào)度,難道我還要去改造原有的方法么?
必然不是的,這時(shí)候我們可以新建一個(gè)動(dòng)態(tài)類繼承QuartzJobBean,并新建自己的業(yè)務(wù)表(例如建一個(gè)jobCaller表),傳入項(xiàng)目方法的全類路徑,這樣我們就可以executeInternal方法里通過(guò)讀表拉取需要調(diào)度的任務(wù)方法,通過(guò)jobDataMap拿到參數(shù),通過(guò)反射直接invoke目標(biāo)方法了,這樣就省去了大量的構(gòu)建調(diào)度任務(wù)的工作了,并且可以在不動(dòng)原有業(yè)務(wù)代碼的基礎(chǔ)上,定向指定任何一個(gè)方法加入任務(wù)調(diào)度了。
ok,talk is cheap, show me the code:

public class DynamicQuartzJob extends QuartzJobBean {
    @Autowired
    private JobScheduleLogManager jobManager;

    @Override
    protected void executeInternal(JobExecutionContext jobContext) {
        try {
            int i = jobManager.trans2JobLogBefore(jobContext);
            if (i <= 0) return;
            JobDetailImpl jobDetail = (JobDetailImpl) jobContext.getJobDetail();
            String name = jobDetail.getName();
            if (StringUtils.isEmpty(name)) {
                throw new JobExecutionException("can not find service info, because desription is empty");
            }
            //注冊(cè)job和trigger的監(jiān)聽器
            JobKey jobKey = jobContext.getJobDetail().getKey();
            TriggerKey triggerKey = jobContext.getTrigger().getKey();
            jobContext.getScheduler().getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(jobKey));
            jobContext.getScheduler().getListenerManager().addTriggerListener(new MyTriggerListener(), KeyMatcher.keyEquals(triggerKey));

            String[] serviceInfo = StringUtils.delimitedListToStringArray(name, ".");
            // serviceInfo[0] is JOB_NAME_PREFIX
            String beanName = serviceInfo[1];
            String methodName = serviceInfo[2];
            Object serviceImpl = getApplicationContext(jobContext).getBean(beanName);
            Method method;
            Class<?>[] parameterTypes = new Class[]{String.class};
            Object[] arguments = null;
            method = serviceImpl.getClass().getMethod(methodName, parameterTypes);
            method.invoke(serviceImpl, jobContext.getJobDetail().getJobDataMap().getString("jobData"));
            jobManager.trans2JobLogAfter(jobContext, i);
        } catch (Exception ex) {
 ErrorLog.errorConvertJson(ApplicationContextWare.getAppName(), LogTreadLocal.getTrackingNo(), this.getClass(), "quartz定時(shí)任務(wù)execute異常", ex);
        }
    }

這里方法簽名參數(shù)我設(shè)定了一個(gè)String類型的形參,其實(shí)可以在添加任務(wù)到jobCaller表的時(shí)候帶上參數(shù),executeInternal的時(shí)候讀表拉取方法簽名。當(dāng)然也可以傳一個(gè)大json,目標(biāo)方法自己解析。

最佳實(shí)踐

這里我新建了一張job_caller表,用于記錄我的jobName(類名.方法名),jobGroup(沒(méi)有就默認(rèn)),jobData(jobDatamap),以及cron表達(dá)式。

image-20190203160812939.png

可以看到我們傳入的時(shí)間規(guī)則是每隔10秒執(zhí)行一次,調(diào)度的是HelloServicesayHello()方法,傳入的參數(shù)是xgj111111.

image-20190203160639805.png

看一下HelloService的sayHello()方法做了什么:

@Component
public class HelloService {

    public void sayHello(String a) {
        System.out.println(a+"======hello world, i am quartz");
    }

    public void callHello(String b) {
        System.out.println(b+"======call");
    }
}

ok,只是簡(jiǎn)單的打印,來(lái)看看效果:

image-20190203160412045.png

每隔10秒(時(shí)間打印忘加了~),sayHello都將會(huì)被執(zhí)行,并且監(jiān)聽器能捕獲到各個(gè)階段。

單節(jié)點(diǎn)服務(wù)重啟調(diào)度恢復(fù)

由于任務(wù)是持久化在表里的,在服務(wù)重啟后,quartz仍然可以去恢復(fù)調(diào)度任務(wù),并且能夠預(yù)先執(zhí)行misfire的任務(wù),這里就不演示了,很簡(jiǎn)單的。

多節(jié)點(diǎn)分布式調(diào)度漂移

這個(gè)就比較有意思了,在多個(gè)節(jié)點(diǎn)調(diào)度確定的任務(wù)時(shí),分布式環(huán)境下,某個(gè)節(jié)點(diǎn)宕機(jī),這個(gè)節(jié)點(diǎn)調(diào)度的作業(yè)能否自動(dòng)漂移到其他節(jié)點(diǎn)?

quartz.properties里,org.quartz.jobStore.isClustered開啟了分布式的配置,此屬性設(shè)置為true,quartz將使用ClusterManager來(lái)初始化節(jié)點(diǎn)。

基于上一個(gè)調(diào)度HelloService#sayHello,我們?cè)傩略鲆粋€(gè)調(diào)度用于調(diào)用HelloService#callHello,同時(shí)新增一個(gè)quartz節(jié)點(diǎn)。(為何第二個(gè)節(jié)點(diǎn)能調(diào)度callHello?==>基于quartz的負(fù)載均衡),如圖:

image-20190203165316261.png
image-20190203165207341.png

啟動(dòng)兩個(gè)服務(wù),分別監(jiān)聽在81238124端口:

image-20190203170522365.png
image-20190203170536220.png

8123調(diào)度的是callHello

image-20190203165407608.png

8124調(diào)度的是sayHello

image-20190203165421036.png

這時(shí)候,我們把8123服務(wù)停掉,看看8124的調(diào)度情況:

停止8123服務(wù):

image-20190203165608268.png

這時(shí)候可以發(fā)現(xiàn),81248123的任務(wù)接管過(guò)來(lái)了:

image-20190203165635596.png

于是可以得出結(jié)論:在分布式場(chǎng)景下,當(dāng)quartz集群的某一臺(tái)服務(wù)宕機(jī),其所調(diào)度的任務(wù)將被其他服務(wù)接管,所以quartz是支持任務(wù)漂移的。

那么如果這時(shí)候,我再講8123起來(lái)會(huì)是什么情況呢?聰明的我和你應(yīng)該都想到了,它由繼續(xù)接管callHello的任務(wù)調(diào)度了。

quartz的缺陷

  • 強(qiáng)依賴于各節(jié)點(diǎn)的系統(tǒng)時(shí)間,多節(jié)點(diǎn)系統(tǒng)時(shí)間不一致將會(huì)出現(xiàn)調(diào)度紊亂的情況
  • 容易造成數(shù)據(jù)庫(kù)死鎖(一個(gè)任務(wù)只能由一個(gè)線程來(lái)調(diào)度,這是由quartz_lock表的行鎖來(lái)實(shí)現(xiàn)的,可以通過(guò)設(shè)置數(shù)據(jù)庫(kù)事務(wù)級(jí)別來(lái)解決,不過(guò)也有說(shuō)設(shè)置了也出現(xiàn)deadlock的)

以上是我的一些基本見解和嘗試。

代碼已上傳至GitHub:https://github.com/xugejunllt/quartz-framework

?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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