您的位置:

详解Quartz集群

随着业务量的增长,单机版Quartz已经难以应对分布式任务管理的需求,此时集群版Quartz就迎刃而解了。集群版Quartz的优点是可以将多台机器作为工作节点来执行任务,这个方案可以提高任务的可靠性、性能和吞吐量。接下来,将从多个方面详细介绍集群版Quartz的实现原理和使用方法。

一、配置文件

Quartz集群的配置可以放在属性文件中,集群中每个节点都需要使用相同的配置文件。下面是一个简单的示例:

org.quartz.scheduler.instanceName = MyClusteredScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 25
org.quartz.threadPool.threadPriority = 5

其中,"org.quartz.scheduler.instanceName"和"org.quartz.scheduler.instanceId"是必须的。

二、消息通讯

Quartz集群依靠JDBC或者RMI方式进行消息通讯,以实现不同节点之间的任务调度信息共享。在消息通讯的过程中,需要注意下面的事项:

1、消息通讯组件不能够出现重复id的节点;

2、消息通讯组件的状态一定要准确。

同时,集群中所有节点的数据库连接信息必须相同。

三、任务执行原则

在Quartz集群模型中,所有的任务都必须实现Job接口,并且该接口应该是StatefulJob的子接口。StatefulJob接口要求任务必须是有状态的并且支持并发执行。这个要求是Quartz集群正常运行必须遵守的规则,并且可以通过实现StatefulJob接口来实现。

为了确保任务被正确分配到集群中各个节点,我们需要在任务类的添加注解@PersistJobDataAfterExecution。这个注解会在执行任务之前保存任务当前状态,并在任务执行完成后更新状态到数据库。这种方式可以确保任务能够正确的在不同节点之间传递。

四、任务负载均衡

任务负载均衡是Quartz集群中特别重要的一个方面。Quartz使用平衡负载策略以确保在集群中任务的平衡的分配。

四种负载均衡策略如下:

1、默认的负载均衡策略:按照最近的一次执行时间靠前的优先被执行;

2、Round Robin策略:所有节点轮流执行任务,每个节点会轮流执行一次。

3、随机策略:随机挑选一个节点去执行任务。

4、基于权重的策略:按照节点配置的权重来分配任务,权重大的节点分配到的任务数量也会比较多。

下面是一个TaskDispatchSelector的示例,可以用来选择节点:

public class TaskDispatchSelector implements LoadBalanceTaskDispatcher{

    public void dispatchTask(List ctxs) {

        JobExecutionContext selectedContext = null;
        try {
            //......

            //根据权重选择节点
            selectedContext = new WeightedLoadBalancedScheduler().instanceIdSchedulerMap.entrySet().stream().max(Comparator.comparingInt((entry) -> entry.getValue().getThreadPool().getBusyThreadCount())).get().getValue().getContext();

            //......

            selectedContext.getScheduler().triggerJob(selectedContext.getJobDetail().getKey());
        } catch (Exception e) {
            throw new MyException("Dispatch Quartz Task Fail.", e);
        }
    }
}

  

五、运行模式

可以在两种运行模式之间切换:

1、只读模式:节点向集群中获取任务并执行。

2、读写模式:节点不仅获取任务,还会去分配或者删除任务。

可以使用JobStoreCMT来实现读写模式。下面是一个JobFactory实现的示例:

public class JobFactory extends AdaptableJobFactory {

    @Autowired
    ApplicationContext applicationContext;

    /**
     * 实例化任务类
     * @param bundle
     * @param method
     * @return
     * @throws Exception
     */
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        JobDetail jobDetail = bundle.getJobDetail();
        Class jobClass = jobDetail.getJobClass();
        return applicationContext.getBean(jobClass);
    }
}

六、集群初始化及关闭

对于集群的初始化,需要确保所有节点都按照相同的配置启动JobScheduler。关闭集群时需要调用shutdown()方法,不要在代码中手动关闭Scheduler。

下面是一个启动和关闭Quartz集群的示例:

public class QuartzClusterRunner {

    public void start() {
        try {
            // 初始化Quartz
            SchedulerFactory sf = new StdSchedulerFactory("quartz.properties");
            Scheduler scheduler = sf.getScheduler();

            //监听Quartz scheduler的启动与关闭事件,和job调度事件
            scheduler.getListenerManager().addSchedulerListener(new MySchedulerListenerAdapter());

            scheduler.start();
            LOGGER.info("Quartz Scheduler started...");

        } catch (Throwable e) {
            LOGGER.error("Quartz Scheduler start error: {}", e.getMessage(), e);
        }
    }

    public void stop() {
        try {
            LOGGER.info("Shutting Quartz Scheduler down...");
            SchedulerFactory sf = new StdSchedulerFactory();  //注意这里没有指定属性文件
            Scheduler scheduler = sf.getScheduler();
            scheduler.shutdown();
            LOGGER.info("Quartz Scheduler shut down.");
        } catch (Throwable e) {
            LOGGER.error("Quartz Scheduler stop error: {}", e.getMessage(), e);
        }
    }
}

致谢

本文档参考了官方文档和网上的一些文章,特此致谢:

1、Quartz官方文档:

http://www.quartz-scheduler.org/

2、博客:

http://blog.csdn.net/endymionwang/article/details/7329144

http://gold.xitu.io/entry/5691b98c60b2ff006f6d741d